From fe44630e1eab248b7e48deed81fc1e7f2837d964 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Mon, 11 May 2026 01:08:49 +0530 Subject: [PATCH 01/20] Add initial implementation for WebBroker API (Protocol Mediation) --- event-gateway/WEBBROKERAPI.md | 1102 +++++++++++++++++ .../configs/gateway-controller/config.toml | 23 + event-gateway/docker-compose.yaml | 6 +- .../cmd/event-gateway/plugins.go | 8 + .../channels-webbrokerapi-example.yaml | 66 + .../gateway-runtime/configs/channels.yaml | 74 +- .../gateway-runtime/configs/config.toml | 3 +- .../internal/binding/loader.go | 12 +- .../gateway-runtime/internal/binding/types.go | 44 +- .../connectors/brokerdriver/kafka/consumer.go | 7 + .../websocket/broker_api_connector.go | 424 +++++++ .../internal/connectors/types.go | 7 + .../gateway-runtime/internal/hub/hub.go | 76 ++ .../internal/runtime/runtime.go | 341 ++++- .../internal/xdsclient/handler.go | 131 +- .../gateway-controller/cmd/controller/main.go | 16 + .../pkg/api/handlers/webbroker_api_handler.go | 187 +++ .../pkg/api/management/webbroker_types.go | 131 ++ .../pkg/eventlistener/api_processor.go | 23 +- .../pkg/models/stored_config.go | 11 +- .../pkg/policyxds/event_channel_translator.go | 107 +- .../pkg/policyxds/snapshot.go | 8 + .../pkg/storage/gateway-controller-db.sql | 8 + .../pkg/storage/sql_store.go | 9 + .../pkg/utils/api_deployment.go | 47 +- 25 files changed, 2718 insertions(+), 153 deletions(-) create mode 100644 event-gateway/WEBBROKERAPI.md create mode 100644 event-gateway/configs/gateway-controller/config.toml create mode 100644 event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml create mode 100644 event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go create mode 100644 gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go create mode 100644 gateway/gateway-controller/pkg/api/management/webbroker_types.go diff --git a/event-gateway/WEBBROKERAPI.md b/event-gateway/WEBBROKERAPI.md new file mode 100644 index 000000000..d4dccdb19 --- /dev/null +++ b/event-gateway/WEBBROKERAPI.md @@ -0,0 +1,1102 @@ +# WebBrokerApi: Protocol Mediation + +## Table of Contents + +- [Overview](#overview) +- [Architecture](#architecture) + - [Key Components](#key-components) + - [Per-Connection Model](#per-connection-model) + - [Message Flows](#message-flows) +- [Policy Enforcement Points](#policy-enforcement-points) +- [Specification Format](#specification-format) +- [Example Use Cases](#example-use-cases) +- [Building and Running](#building-and-running) + - [Option 1: Using Docker Compose (Recommended)](#option-1-using-docker-compose-recommended) + - [Option 2: Building from Source](#option-2-building-from-source) + - [Option 3: Development Mode with Live Reload](#option-3-development-mode-with-live-reload) +- [Testing with Policies](#testing-with-policies) +- [Implementation Details](#implementation-details) +- [Comparison: WebSubApi vs WebBrokerApi](#comparison-websubapi-vs-webbrokerapi) +- [Consumer Group Strategy](#consumer-group-strategy) +- [Topic Subscription](#topic-subscription) +- [Troubleshooting](#troubleshooting) +- [Quick Reference](#quick-reference) +- [Future Enhancements](#future-enhancements) +- [Next Steps](#next-steps) + +## Overview + +**WebBrokerApi** is a new binding type in the Event Gateway that enables **protocol mediation** between web-friendly protocols (WebSocket, SSE) and message brokers (Kafka, MQTT, AMQP). It provides bidirectional streaming with per-connection isolation. + +## Architecture + +### Key Components + +1. **Receiver**: Protocol adapter for web-friendly clients (WebSocket, SSE) +2. **Broker Driver**: Message broker adapter (Kafka, MQTT, AMQP) +3. **Policy Engine**: Message processing with three enforcement points + +### Per-Connection Model + +Each WebSocket connection gets: +- **Inbound Go Channel**: Handles messages from client → broker (produce path) +- **Outbound Go Channel**: Handles messages from broker → client (consume path) +- **Dedicated Kafka Consumer**: Unique consumer group per connection +- **Shared Kafka Producer**: Can publish to any topic dynamically + +### Message Flows + +**Produce Path** (Client → Broker): +``` +WebSocket Client → Receiver → Inbound Channel → on_produce policies → Broker Driver → Kafka +``` + +**Consume Path** (Broker → Client): +``` +Kafka → Broker Driver → Outbound Channel → on_consume policies → Receiver → WebSocket Client +``` + +## Policy Enforcement Points + +Unlike WebSub's `subscribe/inbound/outbound`, WebBrokerApi has: + +| Policy Point | When Applied | Purpose | +|--------------|-------------|---------| +| `on_connection_init.request` | WebSocket handshake (before upgrade) | Authentication, authorization | +| `on_connection_init.response` | WebSocket handshake (after upgrade) | Response customization | +| `on_produce` | Client sends message to broker | Topic mapping, validation, transformation | +| `on_consume` | Broker message delivered to client | Filtering, transformation | + +## Specification Format + +```yaml +kind: WebBrokerApi +apiId: unique-api-identifier +name: api-name +version: v1.0 +context: /base-path + +receiver: + type: websocket # or "sse" in the future + properties: {} + +brokerDriver: + type: kafka # or "mqtt", "amqp" in the future + properties: + topic: default-topic + bootstrap.servers: localhost:9092 + security.protocol: PLAINTEXT + +allChannelPolicies: + on_connection_init: + request: + - name: policy-name + version: v1 + params: {} + response: [] + on_produce: + - name: policy-name + version: v1 + params: {} + on_consume: [] +``` + +## Example Use Cases + +### 1. WebSocket to Kafka with Topic Mapping + +```yaml +channels: + - kind: WebBrokerApi + name: websocket-kafka-api + version: v1.0 + context: /ws-kafka + receiver: + type: websocket + brokerDriver: + type: kafka + properties: + topic: repo-events + bootstrap.servers: localhost:9092 + allChannelPolicies: + on_connection_init: + request: + - name: api-key-auth + version: v1 + params: + in: header + name: X-API-Key + on_produce: + - name: map-topics + version: v1 + params: + extraction: + source: header + key: X-Client-Topic + mappings: + client-issues: kafka-repo-issues + client-commits: kafka-repo-commits + defaultTopic: kafka-repo-events +``` + +**Client Usage:** +```javascript +const ws = new WebSocket('ws://localhost:8080/ws-kafka', { + headers: { + 'X-API-Key': 'your-api-key' + } +}); + +// Produce to specific topic +ws.send(JSON.stringify({ + headers: { + 'X-Client-Topic': 'client-issues' + }, + body: { issue: 'Bug report' } +})); + +// Consume from all subscribed topics +ws.onmessage = (event) => { + console.log('Received:', event.data); +}; +``` + +## Implementation Details + +### Files Modified/Created + +1. **`internal/binding/types.go`** + - Added `WebBrokerApiBinding` struct + - Added `ProtocolMediationPolicies` struct + - Added `ConnectionInitPolicies` struct + +2. **`internal/binding/loader.go`** + - Updated `ParseResult` to include `WebBrokerApiBindings` + - Added `WebBrokerApi` case in parser + +3. **`internal/hub/hub.go`** + - Added `ProcessConnectionInitRequest()` + - Added `ProcessConnectionInitResponse()` + - Added `ProcessProduce()` + - Added `ProcessConsume()` + +4. **`internal/connectors/types.go`** + - Updated `MessageProcessor` interface with new methods + - Added `Topics` field to `ChannelInfo` + +5. **`internal/connectors/receiver/websocket/broker_api_connector.go`** (NEW) + - Implemented `WebBrokerApiReceiver` + - Per-connection bidirectional streaming + - Dedicated Kafka consumer/producer per connection + +6. **`internal/runtime/runtime.go`** + - Added WebBrokerApi processing in `LoadChannels()` + - Added `buildWebBrokerApiPolicyChains()` + +7. **`cmd/event-gateway/plugins.go`** + - Registered `websocket-broker-api` receiver factory + +### Connection Lifecycle + +1. **WebSocket Upgrade**: + - Client sends upgrade request + - `on_connection_init.request` policies applied + - If short-circuited, reject with policy-defined response + - Upgrade to WebSocket + - `on_connection_init.response` policies applied + +2. **Resource Creation**: + - Unique connection ID generated + - Inbound/outbound Go channels created + - Unique Kafka consumer group: `{prefix}-ws-{connID}` + - Kafka consumer subscribes to all relevant topics + - Kafka producer created for publishing + +3. **Message Processing**: + - **Read loop**: WebSocket → Inbound channel + - **Inbound loop**: Inbound channel → on_produce policies → Kafka + - **Outbound loop**: Outbound channel → on_consume policies → WebSocket + - **Consumer callback**: Kafka → Outbound channel + +4. **Connection Close**: + - Stop Kafka consumer + - Close inbound/outbound channels + - Close WebSocket connection + - Clean up connection from registry + +## Comparison: WebSubApi vs WebBrokerApi + +| Aspect | WebSubApi | WebBrokerApi | +|--------|-----------|--------------| +| **Use Case** | Async pub/sub with HTTP callbacks | Bidirectional streaming | +| **Protocol** | HTTP (POST to callbacks) | WebSocket, SSE | +| **Connection** | Stateless HTTP | Persistent streaming | +| **Isolation** | Per-callback consumer group | Per-connection consumer group | +| **Topics** | Multiple channels per API | Dynamic via policies | +| **Policy Points** | subscribe/inbound/outbound | connection_init/produce/consume | +| **Direction** | Unidirectional (gateway → callback) | Bidirectional (client ↔ broker) | + +## Consumer Group Strategy + +Each WebSocket connection gets a **unique consumer group** to ensure: +- Independent consumption (not load-balanced) +- Each client receives all messages +- No message loss on connection drop (offset tracked per connection) + +Consumer group ID format: `{prefix}-ws-{uuid}` + +## Topic Subscription + +For WebBrokerApi: +- Consumer subscribes to **all topics** that might be used (from mappings + default) +- Producer publishes to **dynamic topic** determined by policies +- Policies can inspect message metadata to determine target topic + +## Future Enhancements + +1. **SSE Support**: Add Server-Sent Events receiver +2. **More Brokers**: Add MQTT, AMQP, NATS broker drivers +3. **Topic Discovery**: Dynamic topic subscription based on client requests +4. **Connection Pooling**: Shared consumer groups for load balancing (optional mode) +5. **Backpressure Control**: Configurable buffer sizes and overflow strategies +6. **Metrics**: Per-connection throughput, latency, error rates + +## Building and Running + +The event gateway supports two configuration modes: + +1. **Control Plane Mode (Recommended)**: Configure APIs through the gateway-controller REST API. The controller distributes configurations to the event-gateway via xDS protocol. This is the default mode in Docker Compose. + +2. **Static File Mode**: Configure APIs by editing the `channels.yaml` file directly. Useful for development and testing without the controller. + +### Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) +- [Go 1.24+](https://go.dev/dl/) (for building from source) +- [Kafka](https://kafka.apache.org/) (provided via Docker Compose) + +### Option 1: Using Docker Compose (Recommended) + +This is the easiest way to test protocol mediation with all dependencies. + +#### 1. Start All Services + +From the `event-gateway/` directory: + +```bash +# Copy environment template +cp .env.example .env + +# Start all services (Kafka, Event Gateway, Controller) +docker compose up -d + +# Check status +docker compose ps + +# View logs +docker compose logs -f event-gateway +``` + +This starts: +- **Kafka** on `localhost:9092` (external) and `kafka:29092` (internal) +- **Event Gateway** on `localhost:8081` (WebSocket), `localhost:8080` (HTTP), `localhost:9002` (Admin API) +- **Gateway Controller** on `localhost:9090` (Management API), `localhost:18001` (xDS) + +**Note:** TLS is currently disabled for local development. To enable HTTPS: +1. Generate certificates in `listener-certs/` directory +2. Set `websub_tls_enabled = false` in `gateway-runtime/configs/config.toml` +3. Restart services with `docker compose restart event-gateway` + +#### 2. Create WebBrokerApi via Control Plane (Recommended) + +The gateway runs in control plane mode by default, which means you configure APIs through the gateway-controller REST API: + +```bash +# Create a WebBrokerApi via the gateway-controller +curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Basic YWRtaW46YWRtaW4=' \ +--data '{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { "name": "websocket-kafka-api-v1-0" }, + "spec": { + "displayName": "websocket-kafka-api", + "version": "v1.0", + "context": "/ws-kafka", + "receiver": { + "type": "websocket", + "properties": {} + }, + "brokerDriver": { + "type": "kafka", + "properties": { + "topic": "repo-events", + "bootstrap.servers": "kafka:29092" + } + }, + "allChannelPolicies": { + "onConnectionInit": { + "request": [], + "response": [] + }, + "onProduce": [], + "onConsume": [] + }, + "deploymentState": "deployed" + } + }' +``` + +Verify it was created: + +```bash +# List all WebBrokerApis +curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis \ + -u admin:admin + +# Get specific WebBrokerApi +curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis/websocket-kafka-api-v1-0 \ + -u admin:admin +``` + +The controller automatically distributes the configuration to the event-gateway via xDS. + +**Alternative: Static File Mode** + +If you prefer to use static configuration files instead of the control plane: + +1. Disable control plane in `docker-compose.yaml`: + ```yaml + environment: + - APIP_EGW_CONTROLPLANE_ENABLED=false + ``` + +2. Edit `gateway-runtime/configs/channels.yaml`: + ```yaml + channels: + - kind: WebBrokerApi + apiId: websocket-kafka-api-v1-0 + name: websocket-kafka-api + version: v1.0 + context: /ws-kafka + receiver: + type: websocket + properties: {} + broker-driver: + type: kafka + properties: + topic: repo-events + bootstrap.servers: kafka:29092 + allChannelPolicies: + on_connection_init: + request: [] + response: [] + on_produce: [] + on_consume: [] + ``` + +3. Restart services: + ```bash + docker compose restart event-gateway + ``` + +#### 3. Verify Event Gateway is Running + +```bash +# Health check +curl http://localhost:9002/health +# → {"status":"UP"} + +# Check that xDS distributed the config (should show the WebBrokerApi) +docker compose logs event-gateway | grep "WebBrokerApi" + +# Check WebSocket endpoint +curl -I http://localhost:8081/ws-kafka +# → HTTP/1.1 426 Upgrade Required (means WebSocket endpoint is ready) +``` + +#### 4. Test with WebSocket Client + +**Using wscat (CLI tool):** + +```bash +# Install wscat +npm install -g wscat + +# Connect to the WebSocket endpoint +wscat -c ws://localhost:8081/ws-kafka + +# Once connected, type messages and press Enter to send to Kafka +# You should see messages echoed back as they're consumed from Kafka +``` + +**Using websocat (alternative CLI tool):** + +```bash +# Install websocat +brew install websocat # macOS +# or download from https://github.com/vi/websocat + +# Connect and send/receive messages +websocat ws://localhost:8081/ws-kafka +``` + +**Using Node.js:** + +```javascript +// test-websocket.js +const WebSocket = require('ws'); + +const ws = new WebSocket('ws://localhost:8081/ws-kafka'); + +ws.on('open', () => { + console.log('Connected to Event Gateway'); + + // Send a message to Kafka + ws.send(JSON.stringify({ + message: 'Hello Kafka from WebSocket!', + timestamp: new Date().toISOString() + })); +}); + +ws.on('message', (data) => { + console.log('Received from Kafka:', data.toString()); +}); + +ws.on('close', () => { + console.log('Disconnected'); +}); + +ws.on('error', (error) => { + console.error('Error:', error); +}); +``` + +Run it: +```bash +npm install ws +node test-websocket.js +``` + +**Using Browser Console:** + +```javascript +const ws = new WebSocket('ws://localhost:8081/ws-kafka'); + +ws.onopen = () => { + console.log('Connected!'); + ws.send('Hello from browser!'); +}; + +ws.onmessage = (event) => { + console.log('Received:', event.data); +}; +``` + +#### 5. Monitor Kafka Topics + +```bash +# View messages in Kafka UI +open http://localhost:7080 + +# Or use Kafka CLI +docker exec -it event-gateway-kafka-1 kafka-console-consumer \ + --bootstrap-server localhost:29092 \ + --topic repo-events \ + --from-beginning +``` + +#### 6. Stop Services + +```bash +# Stop all services +docker compose down + +# Stop and remove volumes (clean slate) +docker compose down -v +``` + +### Option 2: Building from Source + +For development or when you need to modify the code. + +#### 1. Build the Event Gateway Runtime + +```bash +cd event-gateway/gateway-runtime + +# Build the binary +go build -o event-gateway ./cmd/event-gateway + +# Or use the Makefile (builds Docker image) +cd .. +make build-gateway-runtime +``` + +#### 2. Start Dependencies (Kafka only) + +```bash +# Start just Kafka from docker-compose +docker compose up kafka -d + +# Wait for Kafka to be ready +docker compose logs -f kafka +``` + +#### 3. Configure Channels + +Create or edit `gateway-runtime/configs/channels.yaml`: + +```yaml +channels: + - kind: WebBrokerApi + name: websocket-kafka-api + version: v1.0 + context: /ws-kafka + receiver: + type: websocket + broker-driver: + type: kafka + properties: + topic: repo-events + bootstrap.servers: localhost:9092 + allChannelPolicies: + on_connection_init: + request: [] + response: [] + on_produce: [] + on_consume: [] +``` + +#### 4. Configure Gateway + +Edit `gateway-runtime/configs/config.toml`: + +```toml +[kafka] +brokers = ["localhost:9092"] +consumer_group_prefix = "egw" + +[server] +websocket_port = 8081 +websub_enabled = false +websub_tls_enabled = false # TLS disabled for local dev +admin_port = 9002 + +[controlplane] +enabled = false # Run in static mode (set to true to use gateway-controller) + +[logging] +level = "info" +``` + +**Note:** When `controlplane.enabled = false`, the gateway reads configuration from the local `channels.yaml` file. When `enabled = true`, it connects to the gateway-controller at `xds_address` and receives configuration via xDS. + +#### 5. Run the Event Gateway + +```bash +cd gateway-runtime + +# Run with config and channels files +./event-gateway \ + -config configs/config.toml \ + -channels configs/channels.yaml + +# Or if you didn't build, run directly with Go +go run ./cmd/event-gateway \ + -config configs/config.toml \ + -channels configs/channels.yaml +``` + +You should see: +``` +INFO Event gateway is ready runtime_id=... +INFO WebBrokerApi WebSocket receiver started channel=websocket-kafka-api context=/ws-kafka topics=[repo-events] +``` + +#### 6. Test the Connection + +Open another terminal and test with websocat: + +```bash +websocat ws://localhost:8081/ws-kafka +``` + +Type messages and press Enter to send them to Kafka. + +#### 7. Verify Messages in Kafka + +```bash +# View messages being published +docker exec -it event-gateway-kafka-1 kafka-console-consumer \ + --bootstrap-server localhost:9092 \ + --topic repo-events \ + --from-beginning +``` + +### Option 3: Development Mode with Live Reload + +For rapid iteration during development. + +#### 1. Install Air (Live Reload Tool) + +```bash +go install github.com/cosmtrek/air@latest +``` + +#### 2. Configure Air + +Create `.air.toml` in `gateway-runtime/`: + +```toml +root = "." +tmp_dir = "tmp" + +[build] +cmd = "go build -o ./tmp/event-gateway ./cmd/event-gateway" +bin = "./tmp/event-gateway -config configs/config.toml -channels configs/channels.yaml" +include_ext = ["go", "toml", "yaml"] +exclude_dir = ["tmp", "vendor"] +delay = 1000 +``` + +#### 3. Run with Live Reload + +```bash +cd gateway-runtime +air +``` + +Now any changes to Go files will automatically rebuild and restart the gateway. + +## Testing with Policies + +### Example: API Key Authentication + +Update your `channels.yaml`: + +```yaml +channels: + - kind: WebBrokerApi + name: secure-websocket-api + version: v1.0 + context: /secure-ws + receiver: + type: websocket + broker-driver: + type: kafka + properties: + topic: secure-events + bootstrap.servers: kafka:29092 + allChannelPolicies: + on_connection_init: + request: + - name: api-key-auth + version: v1 + params: + in: header + name: X-API-Key + on_produce: [] + on_consume: [] +``` + +Test with authentication: + +```bash +# Without API key (should fail) +websocat ws://localhost:8081/secure-ws +# → Connection rejected + +# With API key (should succeed) +websocat --header "X-API-Key: your-api-key" ws://localhost:8081/secure-ws +``` + +### Example: Topic Mapping + +```yaml +allChannelPolicies: + on_produce: + - name: map-topics + version: v1 + params: + extraction: + source: header + key: X-Target-Topic + mappings: + issues: kafka-repo-issues + commits: kafka-repo-commits + prs: kafka-repo-pull-requests + defaultTopic: kafka-repo-events +``` + +Send messages with topic headers: + +```javascript +// In WebSocket message, include metadata for topic routing +ws.send(JSON.stringify({ + headers: { + 'X-Target-Topic': 'issues' + }, + data: { issueId: 123, title: 'Bug fix' } +})); +``` + +## Troubleshooting + +**WebSocket endpoint not available (Empty reply from server):** +- In control plane mode, verify the WebBrokerApi was created via the controller: + ```bash + curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis -u admin:admin + ``` +- Check event-gateway logs for xDS configuration: + ```bash + docker compose logs event-gateway | grep -E "EventChannelConfig|WebBrokerApi" + ``` +- Verify control plane is connected: + ```bash + docker compose logs event-gateway | grep "Connected to xDS" + ``` +- In static file mode, verify `channels.yaml` has the WebBrokerApi entry and `controlplane.enabled = false` + +**Connection rejected during handshake:** +- Check `on_connection_init.request` policies (e.g., API key validation) +- Verify headers are correctly set in upgrade request + +**Messages not reaching Kafka:** +- Check `on_produce` policies for short-circuit conditions +- Verify topic mapping in policies +- Check Kafka broker connectivity + +**Messages not reaching client:** +- Check `on_consume` policies for short-circuit conditions +- Verify WebSocket connection is still open +- Check outbound channel buffer (may be full) + +**High memory usage:** +- Too many concurrent connections +- Adjust buffer sizes in receiver config +- Consider implementing connection limits + +**Connection timeout during Kafka operations:** +- Verify Kafka is running: `docker compose ps kafka` +- Check Kafka logs: `docker compose logs kafka` +- Verify bootstrap servers address (use `kafka:29092` in Docker, `localhost:9092` on host) + +**WebSocket connection closes immediately:** +- Check event gateway logs: `docker compose logs event-gateway` +- Verify the context path matches your channels.yaml config +- Check for policy errors during connection init + +**Gateway container exits with "config.toml is a directory" error:** +- Ensure `configs/gateway-controller/config.toml` is a file, not a directory +- If you accidentally created it as a directory: `rm -rf configs/gateway-controller/config.toml` and recreate as a file +- Restart services: `docker compose up -d` + +**Event gateway exits with "TLS certificate file does not exist":** +- TLS is enabled but certificates are missing in `listener-certs/` directory +- **Solution 1 (Recommended for local dev):** Disable TLS by setting `websub_tls_enabled = false` in `gateway-runtime/configs/config.toml` +- **Solution 2:** Generate self-signed certificates: + ```bash + cd listener-certs/ + openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout default-listener.key \ + -out default-listener.crt \ + -subj "/CN=localhost" + ``` +- After fixing, restart: `docker compose restart event-gateway` + +## Quick Reference + +### Common Commands + +```bash +# Start services +docker compose up -d + +# Stop services +docker compose down + +# View logs +docker compose logs -f event-gateway + +# Restart event gateway only +docker compose restart event-gateway + +# Rebuild after code changes +make build-gateway-runtime +docker compose up -d --build event-gateway + +# WebBrokerApi Management (Control Plane Mode) +# Create WebBrokerApi +curl -X POST http://localhost:9090/api/management/v0.9/webbroker-apis \ + -u admin:admin \ + -H "Content-Type: application/json" \ + -d @webbroker-config.json + +# List WebBrokerApis +curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis \ + -u admin:admin + +# Get WebBrokerApi by ID +curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis/websocket-kafka-api-v1-0 \ + -u admin:admin + +# Delete WebBrokerApi +curl -X DELETE http://localhost:9090/api/management/v0.9/webbroker-apis/websocket-kafka-api-v1-0 \ + -u admin:admin + +# Kafka Management +# List Kafka topics +docker exec event-gateway-kafka-1 kafka-topics \ + --bootstrap-server localhost:29092 --list + +# Create a new Kafka topic +docker exec event-gateway-kafka-1 kafka-topics \ + --bootstrap-server localhost:29092 \ + --create --topic my-topic \ + --partitions 3 --replication-factor 1 + +# Consume from topic +docker exec event-gateway-kafka-1 kafka-console-consumer \ + --bootstrap-server localhost:29092 \ + --topic repo-events \ + --from-beginning + +# Produce to topic +docker exec -it event-gateway-kafka-1 kafka-console-producer \ + --bootstrap-server localhost:29092 \ + --topic repo-events +``` + +### Configuration Reference + +**Event Gateway Environment Variables:** + +| Variable | Description | Default | +|----------|-------------|---------| +| `APIP_EGW_KAFKA_BROKERS` | Kafka broker addresses | `kafka:29092` | +| `APIP_EGW_SERVER_WEBSOCKET_PORT` | WebSocket server port | `8081` | +| `APIP_EGW_SERVER_ADMIN_PORT` | Admin API port | `9002` | +| `APIP_EGW_LOGGING_LEVEL` | Log level (debug/info/warn/error) | `info` | +| `APIP_EGW_CONTROLPLANE_ENABLED` | Enable xDS control plane | `true` | +| `APIP_EGW_CONTROLPLANE_XDS_ADDRESS` | xDS server address | `gateway-controller:18001` | + +**Config File Locations:** + +- Gateway config: `gateway-runtime/configs/config.toml` +- Channels config: `gateway-runtime/configs/channels.yaml` +- Controller config: `configs/gateway-controller/config.toml` + +### Sample WebBrokerApi Configurations + +**Minimal Config (No Policies):** + +```yaml +channels: + - kind: WebBrokerApi + name: simple-ws + version: v1.0 + context: /simple + receiver: + type: websocket + broker-driver: + type: kafka + properties: + topic: simple-events + bootstrap.servers: kafka:29092 + allChannelPolicies: + on_connection_init: + request: [] + response: [] + on_produce: [] + on_consume: [] +``` + +**With Authentication:** + +```yaml +channels: + - kind: WebBrokerApi + name: secure-ws + version: v1.0 + context: /secure + receiver: + type: websocket + broker-driver: + type: kafka + properties: + topic: secure-events + bootstrap.servers: kafka:29092 + allChannelPolicies: + on_connection_init: + request: + - name: api-key-auth + version: v1 + params: + in: header + name: X-API-Key + on_produce: [] + on_consume: [] +``` + +**With Topic Mapping and Transformation:** + +```yaml +channels: + - kind: WebBrokerApi + name: smart-ws + version: v1.0 + context: /smart + receiver: + type: websocket + broker-driver: + type: kafka + properties: + topic: default-events + bootstrap.servers: kafka:29092 + allChannelPolicies: + on_connection_init: + request: + - name: api-key-auth + version: v1 + on_produce: + - name: map-topics + version: v1 + params: + extraction: + source: header + key: X-Event-Type + mappings: + user.created: users-topic + user.updated: users-topic + order.created: orders-topic + defaultTopic: default-events + on_consume: + - name: set-headers + version: v1 + params: + headers: + X-Gateway: event-gateway + X-Timestamp: "${timestamp}" +``` + +### Useful Tools + +**websocat** - WebSocket CLI client: +```bash +# Install +brew install websocat # macOS +cargo install websocat # Linux/Rust + +# Basic usage +websocat ws://localhost:8081/ws-kafka + +# With headers +websocat --header "X-API-Key: test" ws://localhost:8081/secure + +# With text protocol +websocat -t ws://localhost:8081/ws-kafka +``` + +**wscat** - Alternative WebSocket CLI: +```bash +# Install +npm install -g wscat + +# Connect +wscat -c ws://localhost:8081/ws-kafka + +# With headers +wscat -c ws://localhost:8081/secure -H "X-API-Key: test" +``` + +**kcat (kafkacat)** - Kafka CLI tool: +```bash +# Install +brew install kcat # macOS +apt install kafkacat # Linux + +# Consume +kcat -b localhost:9092 -t repo-events -C + +# Produce +echo "test message" | kcat -b localhost:9092 -t repo-events -P +``` + +## Current Spec for WebBroker APIs + +```json +{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "websocket-kafka-api-v1-0" + }, + "spec": { + "displayName": "websocket-kafka-api", + "version": "v1.0", + "context": "/websocket-kafka", + + "receiver": { + "name": "my-websocket-client", + "type": "websocket", + "properties": {} + }, + + "brokerDriver": { + "name": "my-kafka-broker", + "type": "kafka", + "properties": { + "topic": "repo-events", + "bootstrap.servers": "localhost:9092", + "security.protocol": "PLAINTEXT" + } + }, + + "allChannelPolicies": { + "on_connection_init": { + "request": [ + { + "name": "api-key-auth", + "version": "v1", + "params": { + "in": "header", + "name": "X-API-Key" + } + } + ], + "response": [] + }, + "on_produce": [ + { + "name": "map-topics", + "version": "v1", + "params": { + "extraction": { + "source": "header", + "key": "X-Client-Topic" + }, + "mappings": { + "client-issues-topic": "kafka-repo-issues", + "client-commits-topic": "kafka-repo-commits", + "client-pr-topic": "kafka-repo-pull-requests" + }, + "defaultTopic": "kafka-repo-events" + } + } + ], + "on_consume": [] + }, + + "deploymentState": "deployed" + } +} +``` + +## Next Steps + +- Review [WebSub documentation](README.md) for comparison +- Explore [policy development](../gateway/README.md) for custom policies +- Check [performance tuning guide](docs/performance.md) for optimization +- Learn about [monitoring and observability](docs/observability.md) diff --git a/event-gateway/configs/gateway-controller/config.toml b/event-gateway/configs/gateway-controller/config.toml new file mode 100644 index 000000000..9aea2ef6f --- /dev/null +++ b/event-gateway/configs/gateway-controller/config.toml @@ -0,0 +1,23 @@ +# Gateway Controller Configuration + +# TODO We will have to move this to a common place. Separate controller config file is not gonna work + +[controller.server] +gateway_id = "event-gateway-id" + +[controller.logging] +level = "info" + +[controller.controlplane] +insecure_skip_verify = true +gateway_name = "default" +apim_oauth2_client_id = "your-client-id" +apim_oauth2_client_secret = "your-client-secret" + +[controller.auth.basic] +enabled = true + +[[controller.auth.basic.users]] +username = "admin" +password = "admin" +roles = ["admin"] diff --git a/event-gateway/docker-compose.yaml b/event-gateway/docker-compose.yaml index d5c3fd9e7..18f355488 100644 --- a/event-gateway/docker-compose.yaml +++ b/event-gateway/docker-compose.yaml @@ -57,7 +57,7 @@ services: - APIP_EGW_SERVER_WEBSUB_ENABLED=true - APIP_EGW_SERVER_WEBSUB_HTTP_PORT=8080 - APIP_EGW_SERVER_WEBSUB_HTTPS_PORT=8443 - - APIP_EGW_SERVER_WEBSUB_TLS_ENABLED=true + - APIP_EGW_SERVER_WEBSUB_TLS_ENABLED=false # TODO TEST WITH TLS - APIP_EGW_SERVER_WEBSUB_TLS_CERT_FILE=/etc/event-gateway/tls/default-listener.crt - APIP_EGW_SERVER_WEBSUB_TLS_KEY_FILE=/etc/event-gateway/tls/default-listener.key - APIP_EGW_SERVER_WEBSOCKET_PORT=8081 @@ -69,8 +69,8 @@ services: - "host.docker.internal:host-gateway" volumes: - ./listener-certs:/etc/event-gateway/tls:ro - - ./configs/event-gateway/config.toml:/etc/event-gateway/config.toml:ro - - ./configs/event-gateway/channels.yaml:/etc/event-gateway/channels.yaml:ro + - ./gateway-runtime/configs/config.toml:/etc/event-gateway/config.toml:ro + - ./gateway-runtime/configs/channels.yaml:/etc/event-gateway/channels.yaml:ro depends_on: gateway-controller: condition: service_started diff --git a/event-gateway/gateway-runtime/cmd/event-gateway/plugins.go b/event-gateway/gateway-runtime/cmd/event-gateway/plugins.go index af486b29f..f867b0ef1 100644 --- a/event-gateway/gateway-runtime/cmd/event-gateway/plugins.go +++ b/event-gateway/gateway-runtime/cmd/event-gateway/plugins.go @@ -78,4 +78,12 @@ func registerConnectors(registry *connectors.Registry, cfg *config.Config) { ConsumerGroupPrefix: cfg.Kafka.ConsumerGroupPrefix, }) }) + + registry.RegisterReceiver("websocket-broker-api", func(ecfg connectors.ReceiverConfig) (connectors.Receiver, error) { + return websocket.NewBrokerApiReceiver(ecfg, websocket.BrokerApiOptions{ + Port: cfg.Server.WebSocketPort, + ConsumerGroupPrefix: cfg.Kafka.ConsumerGroupPrefix, + Topics: ecfg.Channel.Topics, + }) + }) } diff --git a/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml b/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml new file mode 100644 index 000000000..5e43eb774 --- /dev/null +++ b/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml @@ -0,0 +1,66 @@ +# Example WebBrokerApi Configuration for Protocol Mediation +# This demonstrates bidirectional WebSocket ↔ Kafka streaming + +channels: + # WebBrokerApi example: WebSocket to Kafka protocol mediation + - kind: WebBrokerApi + apiId: websocket-kafka-api-v1-0 + name: websocket-kafka-api + version: v1.0 + context: /websocket-kafka + receiver: + type: websocket + properties: {} + broker-driver: + type: kafka + properties: + topic: repo-events + bootstrap.servers: localhost:9092 + security.protocol: PLAINTEXT + allChannelPolicies: + on_connection_init: + request: + - name: api-key-auth + version: v1 + params: + in: header + name: X-API-Key + response: [] + on_produce: + - name: map-topics + version: v1 + params: + extraction: + source: header + key: X-Client-Topic + mappings: + client-issues-topic: kafka-repo-issues + client-commits-topic: kafka-repo-commits + client-pr-topic: kafka-repo-pull-requests + defaultTopic: kafka-repo-events + on_consume: [] + + # WebSubApi example (for comparison) + - kind: WebSubApi + name: repo-watcher + version: v1 + context: /repos + channels: + - name: issues + - name: pull-requests + receiver: + type: websub + broker-driver: + type: kafka + config: + brokers: + - kafka:29092 + policies: + subscribe: + - name: basic-auth + version: v1 + params: + username: "admin" + password: "admin" + inbound: [] + outbound: [] diff --git a/event-gateway/gateway-runtime/configs/channels.yaml b/event-gateway/gateway-runtime/configs/channels.yaml index bab73c215..609c19b19 100644 --- a/event-gateway/gateway-runtime/configs/channels.yaml +++ b/event-gateway/gateway-runtime/configs/channels.yaml @@ -20,66 +20,22 @@ # outbound: Applied when an event is delivered to a subscriber callback (data delivery). channels: - # WebSubApi example: repo-watcher with multiple channels - - kind: WebSubApi - name: repo-watcher - version: v1 - context: /repos - channels: - - name: issues - - name: pull-requests - - name: commits - receiver: - type: websub - broker-driver: - type: kafka - config: - brokers: - - kafka:29092 - policies: - subscribe: - - name: basic-auth - version: v1 - params: - username: "admin" - password: "admin" - inbound: [] - outbound: [] - - # Protocol Mediation example (WS → Kafka, 1:1 passthrough) - - name: live-prices - mode: protocol-mediation - context: /prices - version: v1 + - kind: WebBrokerApi + apiId: websocket-kafka-api-v1-0 + name: websocket-kafka-api + version: v1.0 + context: /ws-kafka receiver: type: websocket - path: /stream + properties: {} broker-driver: type: kafka - topic: price-updates - config: - brokers: - - kafka:29092 - policies: - subscribe: [] - inbound: [] - outbound: [] - - # WebSubApi with single channel - # - kind: WebSubApi - # name: order-events - # version: v1 - # context: /orders - # channels: - # - name: orders - # receiver: - # type: websub - # broker-driver: - # type: kafka - # config: - # brokers: - # - kafka:29092 - # policies: - # subscribe: [] - # inbound: [] - # outbound: [] + properties: + topic: repo-events + bootstrap.servers: kafka:29092 + allChannelPolicies: + on_connection_init: + request: [] + response: [] + on_produce: [] + on_consume: [] diff --git a/event-gateway/gateway-runtime/configs/config.toml b/event-gateway/gateway-runtime/configs/config.toml index b9f22a467..7b8103d17 100644 --- a/event-gateway/gateway-runtime/configs/config.toml +++ b/event-gateway/gateway-runtime/configs/config.toml @@ -15,7 +15,8 @@ websub_http_port = 8080 # HTTPS port for WebSub server (used when TLS is enabled) websub_https_port = 8443 # Set to true to serve the WebSub hub and webhook receiver over HTTPS. -websub_tls_enabled = true +# TODO: Test WebBrokerApi with TLS enabled (requires generating certificates in listener-certs/) +websub_tls_enabled = false websub_tls_cert_file = "/etc/event-gateway/tls/default-listener.crt" websub_tls_key_file = "/etc/event-gateway/tls/default-listener.key" websocket_port = 8081 diff --git a/event-gateway/gateway-runtime/internal/binding/loader.go b/event-gateway/gateway-runtime/internal/binding/loader.go index 58414e450..1fbad2dcf 100644 --- a/event-gateway/gateway-runtime/internal/binding/loader.go +++ b/event-gateway/gateway-runtime/internal/binding/loader.go @@ -37,13 +37,15 @@ type rawChannelsConfig struct { // ParseResult holds the parsed bindings from a channels YAML file. type ParseResult struct { - Bindings []Binding - WebSubApiBindings []WebSubApiBinding + Bindings []Binding + WebSubApiBindings []WebSubApiBinding + WebBrokerApiBindings []WebBrokerApiBinding } // ParseChannels reads and parses the channels YAML file. // It discriminates entries by the "kind" field: // - "WebSubApi" entries are parsed as WebSubApiBinding (multi-channel per API) +// - "WebBrokerApi" entries are parsed as WebBrokerApiBinding (protocol mediation) // - All other entries are parsed as Binding (legacy flat format) func ParseChannels(filePath string) (*ParseResult, error) { data, err := os.ReadFile(filePath) @@ -70,6 +72,12 @@ func ParseChannels(filePath string) (*ParseResult, error) { return nil, fmt.Errorf("failed to parse WebSubApi entry %d: %w", i, err) } result.WebSubApiBindings = append(result.WebSubApiBindings, wsb) + case "WebBrokerApi": + var wbb WebBrokerApiBinding + if err := node.Decode(&wbb); err != nil { + return nil, fmt.Errorf("failed to parse WebBrokerApi entry %d: %w", i, err) + } + result.WebBrokerApiBindings = append(result.WebBrokerApiBindings, wbb) default: var b Binding if err := node.Decode(&b); err != nil { diff --git a/event-gateway/gateway-runtime/internal/binding/types.go b/event-gateway/gateway-runtime/internal/binding/types.go index f4bca1015..176c97037 100644 --- a/event-gateway/gateway-runtime/internal/binding/types.go +++ b/event-gateway/gateway-runtime/internal/binding/types.go @@ -60,19 +60,49 @@ type ChannelDef struct { Policies PolicyBindings `yaml:"policies"` } +// WebBrokerApiBinding represents a WebBrokerApi for protocol mediation. +// It provides bidirectional streaming between web-friendly protocols (WebSocket, SSE) +// and message brokers (Kafka, MQTT) with per-connection isolation. +type WebBrokerApiBinding struct { + Kind string `yaml:"kind"` // "WebBrokerApi" + APIID string `yaml:"apiId"` + Name string `yaml:"name"` + Version string `yaml:"version"` + Context string `yaml:"context"` + Vhost string `yaml:"vhost"` + Receiver ReceiverSpec `yaml:"receiver"` + BrokerDriver BrokerDriverSpec `yaml:"broker-driver"` + Policies ProtocolMediationPolicies `yaml:"allChannelPolicies"` +} + +// ProtocolMediationPolicies defines policy enforcement points for protocol mediation. +type ProtocolMediationPolicies struct { + OnConnectionInit ConnectionInitPolicies `yaml:"on_connection_init"` + OnProduce []PolicyRef `yaml:"on_produce"` + OnConsume []PolicyRef `yaml:"on_consume"` +} + +// ConnectionInitPolicies defines policies for the connection handshake phase. +type ConnectionInitPolicies struct { + Request []PolicyRef `yaml:"request"` + Response []PolicyRef `yaml:"response"` +} + // ReceiverSpec defines the receiver connector type and configuration. type ReceiverSpec struct { - Type string `yaml:"type"` // "websub" or "websocket" - Path string `yaml:"path"` - Backpressure string `yaml:"backpressure"` // "drop-oldest", "block", "close" + Type string `yaml:"type"` // "websub", "websocket", or "sse" + Path string `yaml:"path"` + Backpressure string `yaml:"backpressure"` // "drop-oldest", "block", "close" + Properties map[string]interface{} `yaml:"properties"` } // BrokerDriverSpec defines the broker-driver connector type and configuration. type BrokerDriverSpec struct { - Type string `yaml:"type"` // "kafka" - Topic string `yaml:"topic"` - Ordering string `yaml:"ordering"` // "ordered" or "unordered" - Config map[string]interface{} `yaml:"config"` // broker-driver-specific config (e.g. brokers, tls) + Type string `yaml:"type"` // "kafka" + Topic string `yaml:"topic"` + Ordering string `yaml:"ordering"` // "ordered" or "unordered" + Config map[string]interface{} `yaml:"config"` // broker-driver-specific config (e.g. brokers, tls) + Properties map[string]interface{} `yaml:"properties"` // Alternative config field for WebBrokerApi } // PolicyBindings holds subscribe, inbound, and outbound policy configurations. diff --git a/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go b/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go index 7430a8b48..d31a26a58 100644 --- a/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go +++ b/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go @@ -90,6 +90,13 @@ func (c *Consumer) consumeLoop(ctx context.Context) { } fetches.EachRecord(func(record *kgo.Record) { + // TODO: Change to debug level before production deployment + slog.Info("[7] Message read from Kafka", + "topic", record.Topic, + "partition", record.Partition, + "offset", record.Offset, + "size_bytes", len(record.Value)) + msg := recordToMessage(record) if err := c.handler(ctx, msg); err != nil { slog.Error("Message handler error", diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go new file mode 100644 index 000000000..03cf253e6 --- /dev/null +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package websocket + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "sync" + + "github.com/google/uuid" + "github.com/gorilla/websocket" + "github.com/wso2/api-platform/event-gateway/gateway-runtime/internal/connectors" +) + +// BrokerApiOptions holds configuration for WebBrokerApi receiver. +type BrokerApiOptions struct { + Port int + ConsumerGroupPrefix string + Topics []string // Topics to subscribe to from broker +} + +// WebBrokerApiReceiver implements protocol mediation for WebBrokerApi. +// Each WebSocket connection gets: +// - Dedicated Kafka consumer (unique consumer group) +// - Dedicated Kafka producer +// - Inbound Go channel (client → broker) +// - Outbound Go channel (broker → client) +type WebBrokerApiReceiver struct { + channel connectors.ChannelInfo + processor connectors.MessageProcessor + brokerDriver connectors.BrokerDriver + opts BrokerApiOptions + mu sync.Mutex + connections map[string]*brokerApiConnection // connID → connection + ctx context.Context +} + +// brokerApiConnection represents a single WebSocket connection with bidirectional channels. +type brokerApiConnection struct { + connID string + ws *websocket.Conn + inbound chan *connectors.Message // client → broker + outbound chan *connectors.Message // broker → client + kafkaConsumer connectors.Receiver + cancel context.CancelFunc + closed bool + mu sync.Mutex +} + +// NewBrokerApiReceiver creates a WebSocket receiver for WebBrokerApi protocol mediation. +func NewBrokerApiReceiver(cfg connectors.ReceiverConfig, opts BrokerApiOptions) (connectors.Receiver, error) { + e := &WebBrokerApiReceiver{ + channel: cfg.Channel, + processor: cfg.Processor, + brokerDriver: cfg.BrokerDriver, + opts: opts, + connections: make(map[string]*brokerApiConnection), + } + + // Register upgrade handler on shared mux. + cfg.Mux.HandleFunc(cfg.Channel.Context, e.handleUpgrade) + + slog.Info("WebBrokerApi receiver registered HTTP handler", + "channel", cfg.Channel.Name, + "path", cfg.Channel.Context, + "mode", cfg.Channel.Mode, + "port", opts.Port) + + return e, nil +} + +// Start initializes the receiver. +func (e *WebBrokerApiReceiver) Start(ctx context.Context) error { + e.ctx = ctx + + // Ensure all topics exist in Kafka. + if len(e.opts.Topics) > 0 { + slog.Info("Ensuring Kafka topics exist", + "channel", e.channel.Name, + "topics", e.opts.Topics) + if err := e.brokerDriver.EnsureTopics(ctx, e.opts.Topics); err != nil { + return fmt.Errorf("failed to ensure kafka topics: %w", err) + } + slog.Info("Kafka topics verified", + "channel", e.channel.Name, + "topics", e.opts.Topics) + } else { + slog.Warn("No Kafka topics configured for WebBrokerApi", + "channel", e.channel.Name) + } + + slog.Info("WebBrokerApi WebSocket receiver started", + "channel", e.channel.Name, + "context", e.channel.Context, + "topics", e.opts.Topics, + "listening_on", fmt.Sprintf("ws://0.0.0.0:%d%s", e.opts.Port, e.channel.Context)) + return nil +} + +// Stop closes all connections. +func (e *WebBrokerApiReceiver) Stop(ctx context.Context) error { + e.mu.Lock() + snapshot := make(map[string]*brokerApiConnection, len(e.connections)) + for k, v := range e.connections { + snapshot[k] = v + } + e.mu.Unlock() + + for _, conn := range snapshot { + e.closeConnection(conn) + } + + return nil +} + +// handleUpgrade handles WebSocket upgrade requests. +func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Request) { + // TODO: Change detailed flow logs ([1]-[8]) to debug level before production deployment + // These Info-level logs are useful for development/testing but should be Debug in production + slog.Info("[1] WebSocket connection attempted", + "channel", e.channel.Name, + "method", r.Method, + "path", r.URL.Path, + "remote_addr", r.RemoteAddr, + "upgrade_header", r.Header.Get("Upgrade"), + "connection_header", r.Header.Get("Connection")) + + // Apply on_connection_init.request policies. + slog.Info("[2] Applying onConnectionInit.request policies", + "channel", e.channel.Name, + "remote_addr", r.RemoteAddr) + + msg := &connectors.Message{ + Headers: r.Header, + Metadata: map[string]interface{}{ + "method": r.Method, + "path": r.URL.Path, + }, + } + + processed, shortCircuited, err := e.processor.ProcessConnectionInitRequest(r.Context(), e.channel.Name, msg) + if err != nil { + slog.Error("[2] onConnectionInit.request policy failed", "channel", e.channel.Name, "error", err) + http.Error(w, "connection init failed", http.StatusForbidden) + return + } + if shortCircuited { + slog.Warn("[2] Connection rejected by onConnectionInit.request policy", "channel", e.channel.Name) + // Policy rejected the connection. + statusCode := http.StatusForbidden + if sc, ok := processed.Metadata["status_code"].(int); ok { + statusCode = sc + } + for k, vals := range processed.Headers { + for _, v := range vals { + w.Header().Add(k, v) + } + } + w.WriteHeader(statusCode) + w.Write(processed.Value) + return + } + + // Update request headers from policy result. + for k, vals := range processed.Headers { + r.Header.Del(k) + for _, v := range vals { + r.Header.Add(k, v) + } + } + + // Upgrade to WebSocket. + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + slog.Error("WebSocket upgrade failed", "error", err) + return + } + + // Apply on_connection_init.response policies (if needed). + slog.Info("[3] Applying onConnectionInit.response policies", "channel", e.channel.Name) + + respMsg := &connectors.Message{ + Headers: map[string][]string{}, + } + if _, err := e.processor.ProcessConnectionInitResponse(r.Context(), e.channel.Name, respMsg); err != nil { + slog.Error("[3] onConnectionInit.response policy failed", "channel", e.channel.Name, "error", err) + ws.Close() + return + } + + // Create per-connection resources. + connID := uuid.New().String() + ctx, cancel := context.WithCancel(e.ctx) + + conn := &brokerApiConnection{ + connID: connID, + ws: ws, + inbound: make(chan *connectors.Message, 256), + outbound: make(chan *connectors.Message, 256), + cancel: cancel, + } + + // Create unique consumer group for this connection. + groupID := fmt.Sprintf("%s-ws-%s", e.opts.ConsumerGroupPrefix, connID) + consumer, err := e.brokerDriver.Subscribe(groupID, e.opts.Topics, func(ctx context.Context, msg *connectors.Message) error { + // Kafka message received → outbound channel. + select { + case conn.outbound <- msg: + case <-ctx.Done(): + return ctx.Err() + default: + slog.Warn("Outbound channel full, dropping message", "connID", connID) + } + return nil + }) + if err != nil { + slog.Error("Failed to create per-connection consumer", "connID", connID, "error", err) + ws.Close() + cancel() + return + } + conn.kafkaConsumer = consumer + + // Start the consumer. + if err := consumer.Start(ctx); err != nil { + slog.Error("Failed to start per-connection consumer", "connID", connID, "error", err) + ws.Close() + cancel() + return + } + + // Register connection. + e.mu.Lock() + e.connections[connID] = conn + e.mu.Unlock() + + slog.Info("[4] WebSocket handshake completed", "connID", connID, "channel", e.channel.Name, "remote", ws.RemoteAddr(), "consumer_group", groupID) + + // Start goroutines for bidirectional communication. + go e.inboundLoop(ctx, conn) + go e.outboundLoop(ctx, conn) + go e.readLoop(ctx, conn) +} + +// readLoop reads WebSocket messages and sends them to the inbound channel. +func (e *WebBrokerApiReceiver) readLoop(ctx context.Context, conn *brokerApiConnection) { + defer e.closeConnection(conn) + + for { + select { + case <-ctx.Done(): + return + default: + } + + msgType, data, err := conn.ws.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) { + slog.Error("WebSocket read error", "connID", conn.connID, "error", err) + } + return + } + + if msgType != websocket.BinaryMessage && msgType != websocket.TextMessage { + continue + } + + slog.Info("[5] Message received from WebSocket client", + "connID", conn.connID, + "channel", e.channel.Name, + "size_bytes", len(data)) + + // Extract headers from WebSocket message (if any). + // For now, we'll just pass the raw data. + msg := &connectors.Message{ + Value: data, + Headers: make(map[string][]string), + } + + select { + case conn.inbound <- msg: + case <-ctx.Done(): + return + default: + slog.Warn("Inbound channel full, dropping message", "connID", conn.connID) + } + } +} + +// inboundLoop processes messages from client → broker. +func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiConnection) { + for { + select { + case <-ctx.Done(): + return + case msg := <-conn.inbound: + // Apply on_produce policies. + slog.Info("[5] Applying onProduce policies", + "connID", conn.connID, + "channel", e.channel.Name, + "message_size", len(msg.Value)) + + processed, shortCircuited, err := e.processor.ProcessProduce(ctx, e.channel.Name, msg) + if err != nil { + slog.Error("[5] onProduce policy failed", "connID", conn.connID, "channel", e.channel.Name, "error", err) + continue + } + if shortCircuited { + slog.Info("[5] Message dropped by onProduce policy", "connID", conn.connID, "channel", e.channel.Name) + continue + } + + // Determine target topic from processed message. + // The topic should be set by policies (e.g., map-topics policy). + targetTopic := processed.Topic + if targetTopic == "" { + // Fallback to default topic. + if len(e.opts.Topics) > 0 { + targetTopic = e.opts.Topics[0] + } else { + slog.Error("No target topic determined for message", "connID", conn.connID) + continue + } + } + + // Publish to Kafka. + slog.Info("[6] Publishing message to Kafka", + "connID", conn.connID, + "channel", e.channel.Name, + "topic", targetTopic, + "message_size", len(processed.Value)) + + if err := e.brokerDriver.Publish(ctx, targetTopic, processed); err != nil { + slog.Error("[6] Failed to publish to Kafka", "connID", conn.connID, "topic", targetTopic, "error", err) + } else { + slog.Info("[6] Message successfully published to Kafka", "connID", conn.connID, "topic", targetTopic) + } + } + } +} + +// outboundLoop processes messages from broker → client. +func (e *WebBrokerApiReceiver) outboundLoop(ctx context.Context, conn *brokerApiConnection) { + for { + select { + case <-ctx.Done(): + return + case msg := <-conn.outbound: + slog.Info("[7] Applying onConsume policies", + "connID", conn.connID, + "channel", e.channel.Name, + "message_size", len(msg.Value)) + + // Apply on_consume policies. + processed, shortCircuited, err := e.processor.ProcessConsume(ctx, e.channel.Name, msg) + if err != nil { + slog.Error("[7] onConsume policy failed", "connID", conn.connID, "channel", e.channel.Name, "error", err) + continue + } + if shortCircuited { + slog.Info("[7] Message dropped by onConsume policy", "connID", conn.connID, "channel", e.channel.Name) + continue + } + + slog.Info("[8] Sending message to WebSocket client", + "connID", conn.connID, + "channel", e.channel.Name, + "message_size", len(processed.Value)) + + // Send to WebSocket client. + if err := conn.ws.WriteMessage(websocket.BinaryMessage, processed.Value); err != nil { + slog.Error("[8] Failed to write to WebSocket", "connID", conn.connID, "error", err) + return + } + } + } +} + +// closeConnection closes a connection and cleans up resources. +func (e *WebBrokerApiReceiver) closeConnection(conn *brokerApiConnection) { + conn.mu.Lock() + if conn.closed { + conn.mu.Unlock() + return + } + conn.closed = true + conn.mu.Unlock() + + conn.cancel() + + if conn.kafkaConsumer != nil { + if err := conn.kafkaConsumer.Stop(context.Background()); err != nil { + slog.Error("Failed to stop per-connection consumer", "connID", conn.connID, "error", err) + } + } + + close(conn.inbound) + close(conn.outbound) + conn.ws.Close() + + e.mu.Lock() + delete(e.connections, conn.connID) + e.mu.Unlock() + + slog.Info("WebSocket connection closed", "connID", conn.connID, "channel", e.channel.Name) +} diff --git a/event-gateway/gateway-runtime/internal/connectors/types.go b/event-gateway/gateway-runtime/internal/connectors/types.go index 6b3139835..bb1ade6f2 100644 --- a/event-gateway/gateway-runtime/internal/connectors/types.go +++ b/event-gateway/gateway-runtime/internal/connectors/types.go @@ -47,6 +47,12 @@ type MessageProcessor interface { ProcessSubscribe(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) ProcessInbound(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) ProcessOutbound(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) + + // Protocol mediation policy enforcement points (WebBrokerApi) + ProcessConnectionInitRequest(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) + ProcessConnectionInitResponse(ctx context.Context, bindingName string, msg *Message) (*Message, error) + ProcessProduce(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) + ProcessConsume(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) } // BrokerDriver manages connections to a backend event system (e.g. Kafka, NATS). @@ -72,6 +78,7 @@ type ChannelInfo struct { Ordering string Channels map[string]string // channel-name → Kafka topic (WebSubApi only) InternalSubTopic string // internal subscription sync topic (WebSubApi only) + Topics []string // topics to subscribe to (WebBrokerApi only) } // RouteMux is an HTTP request multiplexer that supports dynamic route registration. diff --git a/event-gateway/gateway-runtime/internal/hub/hub.go b/event-gateway/gateway-runtime/internal/hub/hub.go index d87908a3e..65d675179 100644 --- a/event-gateway/gateway-runtime/internal/hub/hub.go +++ b/event-gateway/gateway-runtime/internal/hub/hub.go @@ -404,3 +404,79 @@ func summarizeImmediateResponseBody(body []byte) string { } return text } + +// ProcessConnectionInitRequest applies on_connection_init.request policies during connection handshake. +// Used by protocol mediation (WebBrokerApi) for WebSocket upgrade or SSE connection initialization. +// Returns the (possibly mutated) message and whether it was short-circuited. +func (h *Hub) ProcessConnectionInitRequest(ctx context.Context, bindingName string, msg *connectors.Message) (*connectors.Message, bool, error) { + binding := h.GetBinding(bindingName) + if binding == nil { + return nil, false, fmt.Errorf("binding not found: %s", bindingName) + } + + // Apply connection_init request policies if present. + if binding.SubscribeChainKey != "" { // Reuse SubscribeChainKey for on_connection_init.request + chain := h.engine.GetChain(binding.SubscribeChainKey) + if chain != nil { + reqHeaderCtx := MessageToRequestHeaderContext(msg, binding) + result, err := h.engine.ExecuteRequestHeaderPolicies(ctx, binding.SubscribeChainKey, reqHeaderCtx.SharedContext, reqHeaderCtx) + if err != nil { + return nil, false, fmt.Errorf("connection_init request policy execution failed: %w", err) + } + if result.ShortCircuited { + logShortCircuit("Connection init request short-circuited", bindingName, binding.SubscribeChainKey, result.ImmediateResponse) + return immediateResponseToMessage(result.ImmediateResponse), true, nil + } + if err := ApplyRequestHeaderResult(result, msg); err != nil { + return nil, false, fmt.Errorf("failed to apply connection_init request result: %w", err) + } + } + } + + return msg, false, nil +} + +// ProcessConnectionInitResponse applies on_connection_init.response policies during connection handshake. +// Used by protocol mediation (WebBrokerApi) for response customization during handshake. +// Returns the (possibly mutated) message. +func (h *Hub) ProcessConnectionInitResponse(ctx context.Context, bindingName string, msg *connectors.Message) (*connectors.Message, error) { + binding := h.GetBinding(bindingName) + if binding == nil { + return nil, fmt.Errorf("binding not found: %s", bindingName) + } + + // Apply connection_init response policies if present. + // Currently using OutboundChainKey for on_connection_init.response + // In the future, we could add a dedicated field to ChannelBinding if needed. + if binding.OutboundChainKey != "" { + chain := h.engine.GetChain(binding.OutboundChainKey) + if chain != nil { + reqHeaderCtx := MessageToRequestHeaderContext(msg, binding) + result, err := h.engine.ExecuteRequestHeaderPolicies(ctx, binding.OutboundChainKey, reqHeaderCtx.SharedContext, reqHeaderCtx) + if err != nil { + return nil, fmt.Errorf("connection_init response policy execution failed: %w", err) + } + if err := ApplyRequestHeaderResult(result, msg); err != nil { + return nil, fmt.Errorf("failed to apply connection_init response result: %w", err) + } + } + } + + return msg, nil +} + +// ProcessProduce applies on_produce policies when client sends messages to broker. +// Used by protocol mediation (WebBrokerApi) for the produce path (client → broker). +// Returns the (possibly mutated) message and whether it was short-circuited. +func (h *Hub) ProcessProduce(ctx context.Context, bindingName string, msg *connectors.Message) (*connectors.Message, bool, error) { + // Reuse ProcessInbound for on_produce policies + return h.ProcessInbound(ctx, bindingName, msg) +} + +// ProcessConsume applies on_consume policies when broker messages are delivered to client. +// Used by protocol mediation (WebBrokerApi) for the consume path (broker → client). +// Returns the (possibly mutated) message and whether it was short-circuited. +func (h *Hub) ProcessConsume(ctx context.Context, bindingName string, msg *connectors.Message) (*connectors.Message, bool, error) { + // Reuse ProcessOutbound for on_consume policies + return h.ProcessOutbound(ctx, bindingName, msg) +} diff --git a/event-gateway/gateway-runtime/internal/runtime/runtime.go b/event-gateway/gateway-runtime/internal/runtime/runtime.go index 490d279e1..f20b7fd50 100644 --- a/event-gateway/gateway-runtime/internal/runtime/runtime.go +++ b/event-gateway/gateway-runtime/internal/runtime/runtime.go @@ -58,16 +58,15 @@ type Runtime struct { servers []*managedServer // shared servers for port sharing // Dynamic binding management (xDS mode) - mu sync.RWMutex - activeReceivers map[string]connectors.Receiver - activeBrokerDrivers map[string]connectors.BrokerDriver - bindingPaths map[string][]string // name → registered mux paths - bindingTopics map[string][]string // name → Kafka topics (data + internal sub) - websubMux *DynamicMux - websubServer *managedServer - webSubServersCreated bool // true if LoadChannels created WebSub servers - runCtx context.Context - running bool // true after Run() starts servers + mu sync.RWMutex + activeReceivers map[string]connectors.Receiver + activeBrokerDrivers map[string]connectors.BrokerDriver + bindingPaths map[string][]string // name → registered mux paths + bindingTopics map[string][]string // name → Kafka topics (data + internal sub) + websubMux *DynamicMux + wsMux *http.ServeMux // WebSocket mux for dynamic WebBrokerApi bindings + runCtx context.Context + running bool // true after Run() starts servers } type managedServer struct { @@ -106,6 +105,7 @@ func New(cfg *config.Config, rawConfig map[string]interface{}, registry *connect bindingPaths: make(map[string][]string), bindingTopics: make(map[string][]string), websubMux: NewDynamicMux(), + wsMux: http.NewServeMux(), }, nil } @@ -130,6 +130,10 @@ func (r *Runtime) LoadChannels(channelsPath string) error { // Create shared HTTP muxes for port sharing. wsMux := http.NewServeMux() websubMux := http.NewServeMux() + + // Store wsMux for dynamic bindings + r.wsMux = wsMux + hasWS := false hasWebSub := false @@ -286,6 +290,98 @@ func (r *Runtime) LoadChannels(channelsPath string) error { ) } + // Process WebBrokerApi bindings (protocol mediation). + for _, wbb := range parseResult.WebBrokerApiBindings { + vhost := defaultVhost(wbb.Vhost) + + // Build policy chains for protocol mediation. + connInitReqKey, _, produceKey, consumeKey, err := r.buildWebBrokerApiPolicyChains(wbb, vhost) + if err != nil { + return fmt.Errorf("failed to build chains for WebBrokerApi %q: %w", wbb.Name, err) + } + + // Extract topics from broker driver properties. + var topics []string + brokerDriverConfig := wbb.BrokerDriver.Config + if brokerDriverConfig == nil { + brokerDriverConfig = wbb.BrokerDriver.Properties + } + + // Get default topic from BrokerDriver.Topic or properties + defaultTopic := wbb.BrokerDriver.Topic + if defaultTopic == "" { + if topicVal, ok := brokerDriverConfig["topic"]; ok { + if topicStr, ok := topicVal.(string); ok { + defaultTopic = topicStr + } + } + } + if defaultTopic != "" { + topics = append(topics, defaultTopic) + } + + // Register binding in hub. + r.hub.RegisterBinding(hub.ChannelBinding{ + APIID: wbb.APIID, + Name: wbb.Name, + Mode: "protocol-mediation", + Context: wbb.Context, + Version: wbb.Version, + Vhost: vhost, + SubscribeChainKey: connInitReqKey, // on_connection_init.request + InboundChainKey: produceKey, // on_produce + OutboundChainKey: consumeKey, // on_consume + }) + + // Create broker-driver. + brokerDriverType := wbb.BrokerDriver.Type + if brokerDriverType == "" { + brokerDriverType = "kafka" + } + brokerDriver, err := r.registry.CreateBrokerDriver(brokerDriverType, brokerDriverConfig) + if err != nil { + return fmt.Errorf("failed to create broker-driver for WebBrokerApi %q: %w", wbb.Name, err) + } + r.brokerDrivers = append(r.brokerDrivers, brokerDriver) + + hasWS = true + + ch := connectors.ChannelInfo{ + Name: wbb.Name, + Mode: "protocol-mediation", + Context: wbb.Context, + Version: wbb.Version, + Vhost: vhost, + Topics: topics, + } + + // Create WebBrokerApi receiver. + receiverType := wbb.Receiver.Type + if receiverType == "" { + receiverType = "websocket" + } + + ep, err := r.registry.CreateReceiver(receiverType+"-broker-api", connectors.ReceiverConfig{ + Channel: ch, + Processor: r.hub, + BrokerDriver: brokerDriver, + RuntimeID: r.cfg.RuntimeID, + Mux: wsMux, + }) + if err != nil { + return fmt.Errorf("failed to create receiver for WebBrokerApi %q: %w", wbb.Name, err) + } + r.receivers = append(r.receivers, ep) + + slog.Info("Registered WebBrokerApi binding", + "name", wbb.Name, + "context", wbb.Context, + "version", wbb.Version, + "receiver", receiverType, + "topics", topics, + ) + } + // Create shared HTTP servers. if hasWS { wsServer, err := r.newManagedServer("WebSocket", r.cfg.Server.WebSocketPort, wsMux, false) @@ -309,7 +405,6 @@ func (r *Runtime) LoadChannels(channelsPath string) error { } r.servers = append(r.servers, websubHTTPSServer) } - r.webSubServersCreated = true // Mark that LoadChannels created WebSub servers } return nil @@ -327,33 +422,48 @@ func (r *Runtime) Run(ctx context.Context) error { }() } - // If in xDS mode, ensure the websub server is started for dynamic bindings. + // If in xDS mode, ensure servers are started for dynamic bindings. r.mu.Lock() - if !r.webSubServersCreated && r.websubServer == nil && r.cfg.ControlPlane.Enabled && r.cfg.Server.WebSubEnabled { - // Create and start HTTP server - websubHTTPServer, err := r.newManagedServer("WebSub-HTTP", r.cfg.Server.WebSubHTTPPort, r.websubMux, false) + if r.cfg.ControlPlane.Enabled { + // Create WebSocket server for dynamic WebBrokerApi bindings + slog.Info("Creating WebSocket server for dynamic WebBrokerApi bindings", "port", r.cfg.Server.WebSocketPort) + wsServer, err := r.newManagedServer("WebSocket", r.cfg.Server.WebSocketPort, r.wsMux, false) if err != nil { r.mu.Unlock() - return fmt.Errorf("failed to create WebSub HTTP server: %w", err) + return fmt.Errorf("failed to create WebSocket server: %w", err) } - r.servers = append(r.servers, websubHTTPServer) + r.servers = append(r.servers, wsServer) go func() { - r.runServer(websubHTTPServer) + r.runServer(wsServer) }() - // Create and start HTTPS server if TLS is enabled - if r.cfg.Server.WebSubTLSEnabled { - websubHTTPSServer, err := r.newManagedServer("WebSub-HTTPS", r.cfg.Server.WebSubHTTPSPort, r.websubMux, true) + + // Create WebSub servers for dynamic WebSubApi bindings + if r.cfg.Server.WebSubEnabled { + slog.Info("Creating WebSub HTTP server for dynamic WebSubApi bindings", "port", r.cfg.Server.WebSubHTTPPort) + websubHTTPServer, err := r.newManagedServer("WebSub-HTTP", r.cfg.Server.WebSubHTTPPort, r.websubMux, false) if err != nil { r.mu.Unlock() - return fmt.Errorf("failed to create WebSub HTTPS server: %w", err) + return fmt.Errorf("failed to create WebSub HTTP server: %w", err) } - r.websubServer = websubHTTPSServer + r.servers = append(r.servers, websubHTTPServer) go func() { - r.runServer(websubHTTPSServer) + r.runServer(websubHTTPServer) }() + + // Create HTTPS server if TLS is enabled + if r.cfg.Server.WebSubTLSEnabled { + slog.Info("Creating WebSub HTTPS server for dynamic WebSubApi bindings", "port", r.cfg.Server.WebSubHTTPSPort) + websubHTTPSServer, err := r.newManagedServer("WebSub-HTTPS", r.cfg.Server.WebSubHTTPSPort, r.websubMux, true) + if err != nil { + r.mu.Unlock() + return fmt.Errorf("failed to create WebSub HTTPS server: %w", err) + } + r.servers = append(r.servers, websubHTTPSServer) + go func() { + r.runServer(websubHTTPSServer) + }() + } } - // Note: r.websubServer is only set when TLS is enabled; HTTP-only mode leaves it nil - // to avoid double-shutdown since the HTTP server is already in r.servers } r.runCtx = ctx r.running = true @@ -425,16 +535,6 @@ func (r *Runtime) Run(ctx context.Context) error { } } - // Shutdown xDS-mode websub server if created. - r.mu.RLock() - wsSrv := r.websubServer - r.mu.RUnlock() - if wsSrv != nil { - if err := wsSrv.server.Shutdown(shutdownCtx); err != nil { - slog.Error("Failed to shutdown WebSub server", "addr", wsSrv.server.Addr, "error", err) - } - } - if err := r.admin.Stop(shutdownCtx); err != nil { slog.Error("Failed to stop admin server", "error", err) } @@ -619,6 +719,37 @@ func (r *Runtime) buildWebSubApiPolicyChains(wsb binding.WebSubApiBinding, vhost return subscribeKey, inboundKey, outboundKey, channelChainKeys, nil } +func (r *Runtime) buildWebBrokerApiPolicyChains(wbb binding.WebBrokerApiBinding, vhost string) (connInitReqKey, connInitRespKey, produceKey, consumeKey string, err error) { + basePath := wbb.Context + if wbb.Version != "" { + basePath = path.Join(wbb.Context, wbb.Version) + } + + // Connection init request chain: on_connection_init.request policies. + connInitReqKey = binding.GenerateRouteKey("CONNECT_INIT", basePath, vhost) + // Connection init response chain: on_connection_init.response policies (optional). + connInitRespKey = binding.GenerateRouteKey("CONNECT_INIT_RESP", basePath, vhost) + // Produce chain: on_produce policies (client → broker). + produceKey = binding.GenerateRouteKey("PRODUCE", basePath, vhost) + // Consume chain: on_consume policies (broker → client). + consumeKey = binding.GenerateRouteKey("CONSUME", basePath, vhost) + + if err = r.buildChain(connInitReqKey, wbb.Policies.OnConnectionInit.Request); err != nil { + return "", "", "", "", err + } + if err = r.buildChain(connInitRespKey, wbb.Policies.OnConnectionInit.Response); err != nil { + return "", "", "", "", err + } + if err = r.buildChain(produceKey, wbb.Policies.OnProduce); err != nil { + return "", "", "", "", err + } + if err = r.buildChain(consumeKey, wbb.Policies.OnConsume); err != nil { + return "", "", "", "", err + } + + return connInitReqKey, connInitRespKey, produceKey, consumeKey, nil +} + func (r *Runtime) buildChain(routeKey string, policies []binding.PolicyRef) error { if routeKey == "" { return nil @@ -835,6 +966,144 @@ func (r *Runtime) RemoveWebSubApiBinding(name string) error { return nil } +// AddWebBrokerApiBinding dynamically adds a WebBrokerApi binding at runtime. +func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error { + r.mu.Lock() + + vhost := defaultVhost(wbb.Vhost) + + // Build policy chains for the API. + connInitReqKey, _, produceKey, consumeKey, err := r.buildWebBrokerApiPolicyChains(wbb, vhost) + if err != nil { + r.mu.Unlock() + return fmt.Errorf("failed to build chains for WebBrokerApi %q: %w", wbb.Name, err) + } + + // Extract broker-driver properties. + brokerProperties := wbb.BrokerDriver.Config + slog.Info("DEBUG: BrokerDriver config received", "config", brokerProperties, "channel", wbb.Name) + topics := []string{} + if topic, ok := brokerProperties["topic"].(string); ok && topic != "" { + topics = append(topics, topic) + } + slog.Info("DEBUG: Topics extracted", "topics", topics, "channel", wbb.Name) + + r.hub.RegisterBinding(hub.ChannelBinding{ + APIID: wbb.APIID, + Name: wbb.Name, + Mode: "protocol-mediation", + Context: wbb.Context, + Version: wbb.Version, + Vhost: vhost, + SubscribeChainKey: connInitReqKey, + InboundChainKey: produceKey, + OutboundChainKey: consumeKey, + }) + + // Create broker-driver. + brokerDriverType := "kafka" + if wbb.BrokerDriver.Type != "" { + brokerDriverType = wbb.BrokerDriver.Type + } + brokerDriver, err := r.registry.CreateBrokerDriver(brokerDriverType, wbb.BrokerDriver.Config) + if err != nil { + r.mu.Unlock() + return fmt.Errorf("failed to create broker-driver for WebBrokerApi %q: %w", wbb.Name, err) + } + r.activeBrokerDrivers[wbb.Name] = brokerDriver + + ch := connectors.ChannelInfo{ + Name: wbb.Name, + Mode: "protocol-mediation", + Context: wbb.Context, + Version: wbb.Version, + Vhost: vhost, + Topics: topics, + } + + // Determine receiver type (websocket, sse, etc.) + receiverType := "websocket-broker-api" + if wbb.Receiver.Type != "" { + receiverType = wbb.Receiver.Type + "-broker-api" + } + + receiver, err := r.registry.CreateReceiver(receiverType, connectors.ReceiverConfig{ + Channel: ch, + Processor: r.hub, + BrokerDriver: brokerDriver, + RuntimeID: r.cfg.RuntimeID, + Mux: r.wsMux, + }) + if err != nil { + r.mu.Unlock() + return fmt.Errorf("failed to create receiver for WebBrokerApi %q: %w", wbb.Name, err) + } + r.activeReceivers[wbb.Name] = receiver + + startNow := r.running + startCtx := r.runCtx + r.mu.Unlock() + + // If runtime is already running, start the receiver immediately. + if startNow { + if startCtx == nil { + startCtx = context.Background() + } + if err := r.startReceiverWithRetry(startCtx, wbb.Name, receiver); err != nil { + return fmt.Errorf("failed to start receiver for WebBrokerApi %q: %w", wbb.Name, err) + } + } + + slog.Info("Dynamically added WebBrokerApi binding", + "name", wbb.Name, + "context", wbb.Context, + "version", wbb.Version, + "receiver_type", receiverType) + + return nil +} + +// RemoveWebBrokerApiBinding dynamically removes a WebBrokerApi binding at runtime. +func (r *Runtime) RemoveWebBrokerApiBinding(name string) error { + r.mu.Lock() + defer r.mu.Unlock() + + // Stop and remove receiver. + if receiver, ok := r.activeReceivers[name]; ok { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + if err := receiver.Stop(ctx); err != nil { + slog.Error("Failed to stop receiver during removal", "name", name, "error", err) + } + delete(r.activeReceivers, name) + } + + // Close and remove broker driver. + if bd, ok := r.activeBrokerDrivers[name]; ok { + if err := bd.Close(); err != nil { + slog.Error("Failed to close broker-driver during removal", "name", name, "error", err) + } + delete(r.activeBrokerDrivers, name) + } + + // Remove binding chains. + r.unregisterBindingChains(r.hub.GetBinding(name)) + + // Remove hub binding. + r.hub.RemoveBinding(name) + + // Deregister HTTP routes from the mux. + if paths, ok := r.bindingPaths[name]; ok { + for _, p := range paths { + r.websubMux.Remove(p) + } + delete(r.bindingPaths, name) + } + + slog.Info("Dynamically removed WebBrokerApi binding", "name", name) + return nil +} + // Hub returns the hub instance (for testing/inspection). func (r *Runtime) Hub() *hub.Hub { return r.hub diff --git a/event-gateway/gateway-runtime/internal/xdsclient/handler.go b/event-gateway/gateway-runtime/internal/xdsclient/handler.go index 0265070be..2599c62cf 100644 --- a/event-gateway/gateway-runtime/internal/xdsclient/handler.go +++ b/event-gateway/gateway-runtime/internal/xdsclient/handler.go @@ -34,10 +34,12 @@ import ( "github.com/wso2/api-platform/event-gateway/gateway-runtime/internal/binding" ) -// BindingManager can add/remove WebSubApi bindings dynamically. +// BindingManager can add/remove WebSubApi and WebBrokerApi bindings dynamically. type BindingManager interface { AddWebSubApiBinding(wsb binding.WebSubApiBinding) error RemoveWebSubApiBinding(name string) error + AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error + RemoveWebBrokerApiBinding(name string) error } // KafkaConfig holds local Kafka broker settings used as defaults. @@ -47,16 +49,30 @@ type KafkaConfig struct { // EventChannelResource represents the decoded EventChannelConfig JSON payload. type EventChannelResource struct { - UUID string `json:"uuid"` - Name string `json:"name"` - Kind string `json:"kind"` - Context string `json:"context"` - Version string `json:"version"` - Deleted bool `json:"deleted,omitempty"` - Channels []ChannelEntry `json:"channels"` - Receiver ReceiverEntry `json:"receiver"` - BrokerDriver BrokerDriverEntry `json:"brokerDriver"` - Policies PoliciesEntry `json:"policies"` + UUID string `json:"uuid"` + Name string `json:"name"` + Kind string `json:"kind"` + Context string `json:"context"` + Version string `json:"version"` + Deleted bool `json:"deleted,omitempty"` + Channels []ChannelEntry `json:"channels"` + Receiver ReceiverEntry `json:"receiver"` + BrokerDriver BrokerDriverEntry `json:"broker-driver"` + Policies PoliciesEntry `json:"policies"` + AllChannelPolicies *ProtocolMediationPolicies `json:"allChannelPolicies,omitempty"` +} + +// ProtocolMediationPolicies defines policies for WebBrokerApi +type ProtocolMediationPolicies struct { + OnConnectionInit ConnectionInitPolicies `json:"on_connection_init"` + OnProduce []PolicyEntry `json:"on_produce"` + OnConsume []PolicyEntry `json:"on_consume"` +} + +// ConnectionInitPolicies defines policies for connection initialization +type ConnectionInitPolicies struct { + Request []PolicyEntry `json:"request"` + Response []PolicyEntry `json:"response"` } // ChannelEntry represents one channel in the EventChannelConfig. @@ -72,8 +88,8 @@ type ReceiverEntry struct { // BrokerDriverEntry specifies the broker driver configuration. type BrokerDriverEntry struct { - Type string `json:"type"` - Config map[string]interface{} `json:"config"` + Type string `json:"type"` + Properties map[string]interface{} `json:"properties"` } // PoliciesEntry holds the 3-phase policy references. @@ -141,17 +157,19 @@ func (h *Handler) HandleResources(ctx context.Context, resources []*discoveryv3. continue } + slog.Info("DEBUG: Raw JSON from xDS", "json", string(data)) + var ecr EventChannelResource if err := json.Unmarshal(data, &ecr); err != nil { slog.Error("Failed to decode EventChannelConfig", "error", err) continue } - if ecr.UUID == "" { - slog.Warn("EventChannelConfig resource missing UUID, skipping") - continue - } - + slog.Info("DEBUG: Deserialized EventChannelResource", + "uuid", ecr.UUID, + "name", ecr.Name, + "kind", ecr.Kind, + "brokerDriver", ecr.BrokerDriver) // Deletion markers are pushed by the controller to work around // a go-control-plane LinearCache limitation for custom type URLs. // Treat them as absent so the diff logic removes the binding. @@ -166,9 +184,9 @@ func (h *Handler) HandleResources(ctx context.Context, resources []*discoveryv3. // Compute diff: removals for uuid, old := range h.current { if _, exists := incoming[uuid]; !exists { - slog.Info("Removing binding via xDS", "name", old.Name, "uuid", uuid) - if err := h.manager.RemoveWebSubApiBinding(old.Name); err != nil { - slog.Error("Failed to remove binding", "name", old.Name, "error", err) + slog.Info("Removing binding via xDS", "name", old.Name, "uuid", uuid, "kind", old.Kind) + if err := h.removeBinding(old); err != nil { + slog.Error("Failed to remove binding", "name", old.Name, "kind", old.Kind, "error", err) } } } @@ -180,16 +198,15 @@ func (h *Handler) HandleResources(ctx context.Context, resources []*discoveryv3. continue } // Update: remove then re-add - slog.Info("Updating binding via xDS", "name", ecr.Name, "uuid", uuid) - if err := h.manager.RemoveWebSubApiBinding(old.Name); err != nil { - slog.Error("Failed to remove binding for update", "name", old.Name, "error", err) + slog.Info("Updating binding via xDS", "name", ecr.Name, "uuid", uuid, "kind", ecr.Kind) + if err := h.removeBinding(old); err != nil { + slog.Error("Failed to remove binding for update", "name", old.Name, "kind", old.Kind, "error", err) } } else { - slog.Info("Adding binding via xDS", "name", ecr.Name, "uuid", uuid) + slog.Info("Adding binding via xDS", "name", ecr.Name, "uuid", uuid, "kind", ecr.Kind) } - wsb := h.toWebSubApiBinding(ecr) - if err := h.manager.AddWebSubApiBinding(wsb); err != nil { + if err := h.addBinding(ecr); err != nil { return fmt.Errorf("failed to add binding %q: %w", ecr.Name, err) } } @@ -254,7 +271,7 @@ func (h *Handler) resolveBrokerDriver(bd BrokerDriverEntry) binding.BrokerDriver driverType = "kafka" } - cfg := bd.Config + cfg := bd.Properties if len(cfg) == 0 { // Use the event gateway's own Kafka brokers. cfg = map[string]interface{}{ @@ -267,3 +284,61 @@ func (h *Handler) resolveBrokerDriver(bd BrokerDriverEntry) binding.BrokerDriver Config: cfg, } } + +// addBinding routes to the appropriate manager method based on Kind. +func (h *Handler) addBinding(ecr EventChannelResource) error { + switch ecr.Kind { + case "WebSubApi": + wsb := h.toWebSubApiBinding(ecr) + return h.manager.AddWebSubApiBinding(wsb) + case "WebBrokerApi": + wbb := h.toWebBrokerApiBinding(ecr) + return h.manager.AddWebBrokerApiBinding(wbb) + default: + return fmt.Errorf("unsupported kind: %s", ecr.Kind) + } +} + +// removeBinding routes to the appropriate manager method based on Kind. +func (h *Handler) removeBinding(ecr EventChannelResource) error { + switch ecr.Kind { + case "WebSubApi": + return h.manager.RemoveWebSubApiBinding(ecr.Name) + case "WebBrokerApi": + return h.manager.RemoveWebBrokerApiBinding(ecr.Name) + default: + return fmt.Errorf("unsupported kind: %s", ecr.Kind) + } +} + +// toWebBrokerApiBinding converts EventChannelResource to WebBrokerApiBinding. +func (h *Handler) toWebBrokerApiBinding(ecr EventChannelResource) binding.WebBrokerApiBinding { + var connInitReq, connInitResp, onProduce, onConsume []binding.PolicyRef + + if ecr.AllChannelPolicies != nil { + connInitReq = mapPolicyEntries(ecr.AllChannelPolicies.OnConnectionInit.Request) + connInitResp = mapPolicyEntries(ecr.AllChannelPolicies.OnConnectionInit.Response) + onProduce = mapPolicyEntries(ecr.AllChannelPolicies.OnProduce) + onConsume = mapPolicyEntries(ecr.AllChannelPolicies.OnConsume) + } + + return binding.WebBrokerApiBinding{ + Kind: "WebBrokerApi", + APIID: ecr.UUID, + Name: ecr.Name, + Version: ecr.Version, + Context: ecr.Context, + Receiver: binding.ReceiverSpec{ + Type: ecr.Receiver.Type, + }, + BrokerDriver: h.resolveBrokerDriver(ecr.BrokerDriver), + Policies: binding.ProtocolMediationPolicies{ + OnConnectionInit: binding.ConnectionInitPolicies{ + Request: connInitReq, + Response: connInitResp, + }, + OnProduce: onProduce, + OnConsume: onConsume, + }, + } +} diff --git a/gateway/gateway-controller/cmd/controller/main.go b/gateway/gateway-controller/cmd/controller/main.go index 193821b45..6b418415a 100644 --- a/gateway/gateway-controller/cmd/controller/main.go +++ b/gateway/gateway-controller/cmd/controller/main.go @@ -596,6 +596,17 @@ func main() { BaseURL: managementAPIBasePath, }) + // Register WebBrokerApi routes (custom endpoints not yet in OpenAPI spec) + managementGroup := router.Group(managementAPIBasePath) + managementGroup.POST("/webbroker-apis", apiServer.CreateWebBrokerApi) + managementGroup.GET("/webbroker-apis", apiServer.ListWebBrokerApis) + managementGroup.GET("/webbroker-apis/:id", func(c *gin.Context) { + apiServer.GetWebBrokerApiById(c, c.Param("id")) + }) + managementGroup.DELETE("/webbroker-apis/:id", func(c *gin.Context) { + apiServer.DeleteWebBrokerApiById(c, c.Param("id")) + }) + // Also register the same routes on the legacy unprefixed paths for // backwards compatibility. These are deprecated; responses include // RFC 8594 `Deprecation: true` and a `Link` header pointing to the new @@ -760,6 +771,11 @@ func generateAuthConfig(config *config.Config) commonmodels.AuthConfig { "PUT /websub-apis/:id": {"admin", "developer"}, "DELETE /websub-apis/:id": {"admin", "developer"}, + "POST /webbroker-apis": {"admin", "developer"}, + "GET /webbroker-apis": {"admin", "developer"}, + "GET /webbroker-apis/:id": {"admin", "developer"}, + "DELETE /webbroker-apis/:id": {"admin", "developer"}, + "GET /certificates": {"admin", "developer"}, "POST /certificates": {"admin", "developer"}, "DELETE /certificates/:id": {"admin"}, diff --git a/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go b/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go new file mode 100644 index 000000000..b0e0e29db --- /dev/null +++ b/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package handlers + +import ( + "io" + "log/slog" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/wso2/api-platform/common/eventhub" + api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/middleware" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" +) + +// CreateWebBrokerApi handles POST /webbroker-apis +func (s *APIServer) CreateWebBrokerApi(c *gin.Context) { + log := middleware.GetLogger(c, s.logger) + + body, err := io.ReadAll(c.Request.Body) + if err != nil { + log.Error("Failed to read request body", slog.Any("error", err)) + c.JSON(http.StatusBadRequest, api.ErrorResponse{ + Status: "error", + Message: "Failed to read request body", + }) + return + } + + correlationID := middleware.GetCorrelationID(c) + + result, err := s.deploymentService.DeployAPIConfiguration(utils.APIDeploymentParams{ + Data: body, + ContentType: c.GetHeader("Content-Type"), + Kind: "WebBrokerApi", + APIID: "", + Origin: models.OriginGatewayAPI, + CorrelationID: correlationID, + Logger: log, + }) + if err != nil { + log.Error("Failed to deploy WebBrokerApi configuration", slog.Any("error", err)) + if storage.IsConflictError(err) { + c.JSON(http.StatusConflict, api.ErrorResponse{ + Status: "error", + Message: err.Error(), + }) + return + } + if mapRenderError(c, "create", err) { + return + } + c.JSON(http.StatusBadRequest, api.ErrorResponse{ + Status: "error", + Message: err.Error(), + }) + return + } + + cfg := result.StoredConfig + + c.JSON(http.StatusCreated, buildResourceResponseFromStored(cfg.SourceConfiguration, cfg)) + + if result.IsStale { + return + } + + if s.controlPlaneClient != nil && s.controlPlaneClient.IsConnected() && s.systemConfig.Controller.ControlPlane.DeploymentPushEnabled { + go s.waitForDeploymentAndPush(cfg.UUID, correlationID, log) + } +} + +// ListWebBrokerApis handles GET /webbroker-apis +func (s *APIServer) ListWebBrokerApis(c *gin.Context) { + configs, err := s.db.GetAllConfigsByKind(string(models.KindWebBrokerApi)) + if err != nil { + s.logger.Error("Failed to list WebBrokerApis", slog.Any("error", err)) + c.JSON(http.StatusInternalServerError, api.ErrorResponse{ + Status: "error", + Message: "Failed to list WebBrokerApi configurations", + }) + return + } + + items := make([]any, 0, len(configs)) + for _, cfg := range configs { + items = append(items, buildResourceResponseFromStored(cfg.SourceConfiguration, cfg)) + } + + c.JSON(http.StatusOK, gin.H{ + "status": "success", + "count": len(items), + "webBrokerApis": items, + }) +} + +// GetWebBrokerApiById handles GET /webbroker-apis/{id} +func (s *APIServer) GetWebBrokerApiById(c *gin.Context, id string) { + log := middleware.GetLogger(c, s.logger) + handle := id + + cfg, err := s.db.GetConfigByKindAndHandle(models.KindWebBrokerApi, handle) + if err != nil { + if storage.IsDatabaseUnavailableError(err) { + log.Error("Database unavailable", slog.Any("error", err)) + c.JSON(http.StatusServiceUnavailable, api.ErrorResponse{ + Status: "error", + Message: "Database is temporarily unavailable", + }) + return + } + log.Warn("WebBrokerApi not found", slog.String("handle", handle)) + c.JSON(http.StatusNotFound, api.ErrorResponse{ + Status: "error", + Message: "WebBrokerApi not found", + }) + return + } + + c.JSON(http.StatusOK, buildResourceResponseFromStored(cfg.SourceConfiguration, cfg)) +} + +// DeleteWebBrokerApiById handles DELETE /webbroker-apis/{id} +func (s *APIServer) DeleteWebBrokerApiById(c *gin.Context, id string) { + log := middleware.GetLogger(c, s.logger) + handle := id + + cfg, err := s.db.GetConfigByKindAndHandle(models.KindWebBrokerApi, handle) + if err != nil { + if storage.IsDatabaseUnavailableError(err) { + log.Error("Database unavailable", slog.Any("error", err)) + c.JSON(http.StatusServiceUnavailable, api.ErrorResponse{ + Status: "error", + Message: "Database is temporarily unavailable", + }) + return + } + log.Warn("WebBrokerApi not found", slog.String("handle", handle)) + c.JSON(http.StatusNotFound, api.ErrorResponse{ + Status: "error", + Message: "WebBrokerApi not found", + }) + return + } + + correlationID := middleware.GetCorrelationID(c) + + if err := s.db.DeleteConfig(cfg.UUID); err != nil { + log.Error("Failed to delete WebBrokerApi from database", slog.Any("error", err)) + c.JSON(http.StatusInternalServerError, api.ErrorResponse{ + Status: "error", + Message: "Failed to delete configuration", + }) + return + } + + // Publish delete event for xDS propagation + s.publishWebSubEvent(eventhub.EventTypeAPI, "DELETE", cfg.UUID, correlationID, log) + + log.Info("WebBrokerApi deleted successfully", + slog.String("id", id), + slog.String("correlation_id", correlationID)) + + c.JSON(http.StatusOK, gin.H{ + "status": "success", + "message": "WebBrokerApi deleted successfully", + }) +} diff --git a/gateway/gateway-controller/pkg/api/management/webbroker_types.go b/gateway/gateway-controller/pkg/api/management/webbroker_types.go new file mode 100644 index 000000000..5b6bb8ac6 --- /dev/null +++ b/gateway/gateway-controller/pkg/api/management/webbroker_types.go @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package management + +// WebBrokerApi defines model for WebBrokerApi. +type WebBrokerApi struct { + // ApiVersion API specification version + ApiVersion WebBrokerApiApiVersion `json:"apiVersion" yaml:"apiVersion"` + + // Kind API type + Kind WebBrokerApiKind `json:"kind" yaml:"kind"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec WebBrokerApiData `json:"spec" yaml:"spec"` + + // Status Server-managed lifecycle fields. Populated on responses. + Status *ResourceStatus `json:"status,omitempty" yaml:"status,omitempty"` +} + +// WebBrokerApiApiVersion API specification version +type WebBrokerApiApiVersion string + +// WebBrokerApiKind API type +type WebBrokerApiKind string + +// WebBrokerApiRequest defines model for WebBrokerApiRequest. +type WebBrokerApiRequest struct { + // ApiVersion API specification version + ApiVersion WebBrokerApiRequestApiVersion `json:"apiVersion" yaml:"apiVersion"` + + // Kind API type + Kind WebBrokerApiRequestKind `json:"kind" yaml:"kind"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec WebBrokerApiData `json:"spec" yaml:"spec"` +} + +// WebBrokerApiRequestApiVersion API specification version +type WebBrokerApiRequestApiVersion string + +// WebBrokerApiRequestKind API type +type WebBrokerApiRequestKind string + +// WebBrokerApiData defines spec for WebBrokerApi. +type WebBrokerApiData struct { + // Context Base path for all API routes (must start with /, no trailing slash) + Context string `json:"context" yaml:"context"` + + // DeploymentState Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment. + DeploymentState *WebBrokerApiDataDeploymentState `json:"deploymentState,omitempty" yaml:"deploymentState,omitempty"` + + // DisplayName Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed) + DisplayName string `json:"displayName" yaml:"displayName"` + + // Receiver Receiver configuration - protocol adapter for web-friendly clients (WebSocket, SSE) + Receiver WebBrokerApiReceiver `json:"receiver" yaml:"receiver"` + + // BrokerDriver Broker driver configuration - message broker adapter (Kafka, MQTT, AMQP) + BrokerDriver WebBrokerApiBrokerDriver `json:"brokerDriver" yaml:"brokerDriver"` + + // AllChannelPolicies Protocol mediation policies applied to all channels + AllChannelPolicies *WebBrokerApiPolicies `json:"allChannelPolicies,omitempty" yaml:"allChannelPolicies,omitempty"` + + // Version Semantic version of the API + Version string `json:"version" yaml:"version"` + + // Vhosts Custom virtual hosts/domains for the API + Vhosts *struct { + // Main Custom virtual host/domain for production traffic + Main string `json:"main" yaml:"main"` + + // Sandbox Custom virtual host/domain for sandbox traffic + Sandbox *string `json:"sandbox,omitempty" yaml:"sandbox,omitempty"` + } `json:"vhosts,omitempty" yaml:"vhosts,omitempty"` +} + +// WebBrokerApiDataDeploymentState Desired deployment state +type WebBrokerApiDataDeploymentState string + +// WebBrokerApiReceiver Receiver configuration for protocol adapter +type WebBrokerApiReceiver struct { + // Type Receiver type (websocket, sse) + Type string `json:"type" yaml:"type"` + + // Properties Receiver-specific configuration properties + Properties *map[string]interface{} `json:"properties,omitempty" yaml:"properties,omitempty"` +} + +// WebBrokerApiBrokerDriver Broker driver configuration +type WebBrokerApiBrokerDriver struct { + // Type Broker driver type (kafka, mqtt, amqp) + Type string `json:"type" yaml:"type"` + + // Properties Broker-specific configuration properties (topic, bootstrap.servers, etc.) + Properties map[string]interface{} `json:"properties" yaml:"properties"` +} + +// WebBrokerApiPolicies Protocol mediation policies +type WebBrokerApiPolicies struct { + // OnConnectionInit Policies applied during WebSocket handshake + OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"onConnectionInit,omitempty" yaml:"onConnectionInit,omitempty"` + + // OnProduce Policies applied when client sends message to broker + OnProduce *[]Policy `json:"onProduce,omitempty" yaml:"onProduce,omitempty"` + + // OnConsume Policies applied when broker message delivered to client + OnConsume *[]Policy `json:"onConsume,omitempty" yaml:"onConsume,omitempty"` +} + +// WebBrokerApiConnectionInitPolicies Connection initialization policies +type WebBrokerApiConnectionInitPolicies struct { + // Request Policies applied before WebSocket upgrade + Request *[]Policy `json:"request,omitempty" yaml:"request,omitempty"` + + // Response Policies applied after WebSocket upgrade + Response *[]Policy `json:"response,omitempty" yaml:"response,omitempty"` +} diff --git a/gateway/gateway-controller/pkg/eventlistener/api_processor.go b/gateway/gateway-controller/pkg/eventlistener/api_processor.go index a175f3363..2905caad6 100644 --- a/gateway/gateway-controller/pkg/eventlistener/api_processor.go +++ b/gateway/gateway-controller/pkg/eventlistener/api_processor.go @@ -107,8 +107,10 @@ func (l *EventListener) handleAPICreateOrUpdate(event eventhub.Event) { } } - // Update xDS snapshot - l.updateSnapshotAsync(entityID, event.EventID, "Failed to update xDS snapshot after replica sync") + // Update xDS snapshot for REST APIs only (WebSubApi and WebBrokerApi use Policy xDS) + if storedConfig.Kind != models.KindWebSubApi && storedConfig.Kind != models.KindWebBrokerApi { + l.updateSnapshotAsync(entityID, event.EventID, "Failed to update xDS snapshot after replica sync") + } // Update policies l.updatePoliciesForAPI(storedConfig, event.EventID) @@ -184,16 +186,19 @@ func (l *EventListener) handleAPIDelete(event eventhub.Event) { } } - // Update xDS snapshot - l.updateSnapshotAsync(entityID, event.EventID, "Failed to update xDS snapshot after API deletion") + // Update xDS snapshot for REST APIs only (WebSubApi and WebBrokerApi use Policy xDS) + if existingConfig == nil || (existingConfig.Kind != models.KindWebSubApi && existingConfig.Kind != models.KindWebBrokerApi) { + l.updateSnapshotAsync(entityID, event.EventID, "Failed to update xDS snapshot after API deletion") + } // Remove runtime config for the deleted API if l.policyManager != nil && existingConfig != nil { - if existingConfig.Kind == models.KindWebSubApi { - // WebSubApi: refresh event channel cache (config already removed from ConfigStore) + if existingConfig.Kind == models.KindWebSubApi || existingConfig.Kind == models.KindWebBrokerApi { + // WebSubApi/WebBrokerApi: refresh event channel cache (config already removed from ConfigStore) if err := l.policyManager.UpdateEventChannelSnapshot(); err != nil { - l.logger.Warn("Failed to update event channel snapshot after WebSubApi deletion", + l.logger.Warn("Failed to update event channel snapshot after event API deletion", slog.String("api_id", entityID), + slog.String("kind", string(existingConfig.Kind)), slog.Any("error", err)) } } else if err := l.policyManager.DeleteAPIConfig(existingConfig.Kind, existingConfig.Handle); err != nil { @@ -214,8 +219,8 @@ func (l *EventListener) updatePoliciesForAPI(cfg *models.StoredConfig, correlati return } - if cfg.Kind == models.KindWebSubApi { - // WebSubApi doesn't need RuntimeDeployConfig transformation. + if cfg.Kind == models.KindWebSubApi || cfg.Kind == models.KindWebBrokerApi { + // WebSubApi and WebBrokerApi don't need RuntimeDeployConfig transformation. // Just refresh the event channel config cache. if err := l.policyManager.UpdateEventChannelSnapshot(); err != nil { l.logger.Error("Failed to update event channel snapshot", diff --git a/gateway/gateway-controller/pkg/models/stored_config.go b/gateway/gateway-controller/pkg/models/stored_config.go index 526174070..1ffd4d912 100644 --- a/gateway/gateway-controller/pkg/models/stored_config.go +++ b/gateway/gateway-controller/pkg/models/stored_config.go @@ -32,11 +32,12 @@ import ( type ArtifactKind = string const ( - KindRestApi ArtifactKind = "RestApi" - KindWebSubApi ArtifactKind = "WebSubApi" - KindMcp ArtifactKind = "Mcp" - KindLlmProxy ArtifactKind = "LlmProxy" - KindLlmProvider ArtifactKind = "LlmProvider" + KindRestApi ArtifactKind = "RestApi" + KindWebSubApi ArtifactKind = "WebSubApi" + KindWebBrokerApi ArtifactKind = "WebBrokerApi" + KindMcp ArtifactKind = "Mcp" + KindLlmProxy ArtifactKind = "LlmProxy" + KindLlmProvider ArtifactKind = "LlmProvider" ) // DesiredState represents the intended deployment state of an API configuration. diff --git a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go index ee944880f..b66bda525 100644 --- a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go +++ b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go @@ -46,7 +46,7 @@ func (t *Translator) TranslateWebSubApisToEventChannelConfigs(configs []*models. continue } - resource, err := t.buildEventChannelResource(cfg.UUID, &webSubCfg) + resource, err := t.buildEventChannelResourceForWebSub(cfg.UUID, &webSubCfg) if err != nil { t.logger.Error("Failed to build EventChannelConfig resource", slog.String("uuid", cfg.UUID), @@ -64,7 +64,45 @@ func (t *Translator) TranslateWebSubApisToEventChannelConfigs(configs []*models. return resources } -func (t *Translator) buildEventChannelResource(uuid string, webSubCfg *api.WebSubAPI) (types.Resource, error) { +// TranslateWebBrokerApisToEventChannelConfigs translates WebBrokerApi StoredConfigs into +// EventChannelConfig xDS resources for the event gateway runtime. +func (t *Translator) TranslateWebBrokerApisToEventChannelConfigs(configs []*models.StoredConfig) map[string]types.Resource { + resources := make(map[string]types.Resource) + + for _, cfg := range configs { + if cfg.Kind != models.KindWebBrokerApi { + continue + } + if cfg.DesiredState != models.StateDeployed { + continue + } + + webBrokerCfg, ok := cfg.Configuration.(api.WebBrokerApi) + if !ok { + t.logger.Warn("Failed to type-assert WebBrokerApi configuration", + slog.String("uuid", cfg.UUID)) + continue + } + + resource, err := t.buildEventChannelResourceForWebBroker(cfg.UUID, &webBrokerCfg) + if err != nil { + t.logger.Error("Failed to build EventChannelConfig resource for WebBrokerApi", + slog.String("uuid", cfg.UUID), + slog.Any("error", err)) + continue + } + + resources[cfg.UUID] = resource + } + + t.logger.Info("Translated WebBrokerApis to EventChannelConfig resources", + slog.Int("input_configs", len(configs)), + slog.Int("output_resources", len(resources))) + + return resources +} + +func (t *Translator) buildEventChannelResourceForWebSub(uuid string, webSubCfg *api.WebSubAPI) (types.Resource, error) { spec := webSubCfg.Spec // Build channels list from hub channels, including per-channel subscribe policies. @@ -116,6 +154,71 @@ func (t *Translator) buildEventChannelResource(uuid string, webSubCfg *api.WebSu return toAnyResource(data, EventChannelConfigTypeURL) } +func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBrokerCfg *api.WebBrokerApi) (types.Resource, error) { + spec := webBrokerCfg.Spec + + // Build receiver configuration + receiver := map[string]interface{}{ + "type": spec.Receiver.Type, + } + if spec.Receiver.Properties != nil { + receiver["properties"] = *spec.Receiver.Properties + } + + // Build broker-driver configuration + brokerDriver := map[string]interface{}{ + "type": spec.BrokerDriver.Type, + "properties": spec.BrokerDriver.Properties, + } + slog.Info("DEBUG: Building EventChannelConfig for WebBrokerApi", + "name", webBrokerCfg.Metadata.Name, + "brokerDriverType", spec.BrokerDriver.Type, + "brokerDriverConfig", spec.BrokerDriver.Properties) + + // Build protocol mediation policies + var onConnectionInitRequest []interface{} + var onConnectionInitResponse []interface{} + var onProduce []interface{} + var onConsume []interface{} + + if spec.AllChannelPolicies != nil { + if spec.AllChannelPolicies.OnConnectionInit != nil { + if spec.AllChannelPolicies.OnConnectionInit.Request != nil { + onConnectionInitRequest = buildPolicyList(spec.AllChannelPolicies.OnConnectionInit.Request) + } + if spec.AllChannelPolicies.OnConnectionInit.Response != nil { + onConnectionInitResponse = buildPolicyList(spec.AllChannelPolicies.OnConnectionInit.Response) + } + } + if spec.AllChannelPolicies.OnProduce != nil { + onProduce = buildPolicyList(spec.AllChannelPolicies.OnProduce) + } + if spec.AllChannelPolicies.OnConsume != nil { + onConsume = buildPolicyList(spec.AllChannelPolicies.OnConsume) + } + } + + data := map[string]interface{}{ + "uuid": uuid, + "name": string(webBrokerCfg.Metadata.Name), + "kind": "WebBrokerApi", + "context": spec.Context, + "version": spec.Version, + "receiver": receiver, + "broker-driver": brokerDriver, + "allChannelPolicies": map[string]interface{}{ + "on_connection_init": map[string]interface{}{ + "request": onConnectionInitRequest, + "response": onConnectionInitResponse, + }, + "on_produce": onProduce, + "on_consume": onConsume, + }, + } + + return toAnyResource(data, EventChannelConfigTypeURL) +} + func buildPolicyList(policies *[]api.Policy) []interface{} { if policies == nil || len(*policies) == 0 { return []interface{}{} diff --git a/gateway/gateway-controller/pkg/policyxds/snapshot.go b/gateway/gateway-controller/pkg/policyxds/snapshot.go index d4a39c8cd..b4d71a236 100644 --- a/gateway/gateway-controller/pkg/policyxds/snapshot.go +++ b/gateway/gateway-controller/pkg/policyxds/snapshot.go @@ -151,6 +151,14 @@ func (sm *SnapshotManager) UpdateSnapshot(ctx context.Context) error { if sm.configStore != nil { eventChannelResources := sm.translator.TranslateWebSubApisToEventChannelConfigs(sm.configStore.GetAllByKind("WebSubApi")) + // Also translate WebBrokerApi configs + webBrokerResources := sm.translator.TranslateWebBrokerApisToEventChannelConfigs(sm.configStore.GetAllByKind("WebBrokerApi")) + + // Merge both resource maps + for uuid, resource := range webBrokerResources { + eventChannelResources[uuid] = resource + } + // The go-control-plane LinearCache does not notify SotW wildcard watches // when resources are only deleted (non-full-state custom type URL). // Work around this by pushing a deletion marker via UpdateResource before diff --git a/gateway/gateway-controller/pkg/storage/gateway-controller-db.sql b/gateway/gateway-controller/pkg/storage/gateway-controller-db.sql index e2bb79b68..bd0b91967 100644 --- a/gateway/gateway-controller/pkg/storage/gateway-controller-db.sql +++ b/gateway/gateway-controller/pkg/storage/gateway-controller-db.sql @@ -44,6 +44,14 @@ CREATE TABLE IF NOT EXISTS websub_apis ( FOREIGN KEY(gateway_id, uuid) REFERENCES artifacts(gateway_id, uuid) ON DELETE CASCADE ); +CREATE TABLE IF NOT EXISTS webbroker_apis ( + uuid TEXT NOT NULL, + gateway_id TEXT NOT NULL, + configuration TEXT NOT NULL, + PRIMARY KEY (gateway_id, uuid), + FOREIGN KEY(gateway_id, uuid) REFERENCES artifacts(gateway_id, uuid) ON DELETE CASCADE +); + CREATE TABLE IF NOT EXISTS llm_providers ( uuid TEXT NOT NULL, gateway_id TEXT NOT NULL, diff --git a/gateway/gateway-controller/pkg/storage/sql_store.go b/gateway/gateway-controller/pkg/storage/sql_store.go index 7d155f79d..a3cdfc966 100644 --- a/gateway/gateway-controller/pkg/storage/sql_store.go +++ b/gateway/gateway-controller/pkg/storage/sql_store.go @@ -138,6 +138,8 @@ func kindToResourceTable(kind string) (string, error) { return "rest_apis", nil case "WebSubApi": return "websub_apis", nil + case "WebBrokerApi": + return "webbroker_apis", nil case "LlmProvider": return "llm_providers", nil case "LlmProxy": @@ -170,6 +172,13 @@ func unmarshalSourceConfig(cfg *models.StoredConfig, jsonData string) error { } cfg.SourceConfiguration = config cfg.Configuration = config + case "WebBrokerApi": + var config api.WebBrokerApi + if err := json.Unmarshal([]byte(jsonData), &config); err != nil { + return fmt.Errorf("failed to unmarshal configuration: %w", err) + } + cfg.SourceConfiguration = config + cfg.Configuration = config case "LlmProvider": var config api.LLMProviderConfiguration if err := json.Unmarshal([]byte(jsonData), &config); err != nil { diff --git a/gateway/gateway-controller/pkg/utils/api_deployment.go b/gateway/gateway-controller/pkg/utils/api_deployment.go index c6c7819e9..ca5a98fd3 100644 --- a/gateway/gateway-controller/pkg/utils/api_deployment.go +++ b/gateway/gateway-controller/pkg/utils/api_deployment.go @@ -202,6 +202,15 @@ func (s *APIDeploymentService) DeployAPIConfiguration(params APIDeploymentParams kind = string(webSubConfig.Kind) parsedConfig = webSubConfig annotationArtifactID = annotationValue(webSubConfig.Metadata.Annotations, commonconstants.AnnotationArtifactID) + case "WebBrokerApi": + var webBrokerConfig api.WebBrokerApi + if err := s.parser.Parse(params.Data, params.ContentType, &webBrokerConfig); err != nil { + return nil, fmt.Errorf("failed to parse configuration: %w", err) + } + handle = webBrokerConfig.Metadata.Name + kind = string(webBrokerConfig.Kind) + parsedConfig = webBrokerConfig + annotationArtifactID = annotationValue(webBrokerConfig.Metadata.Annotations, commonconstants.AnnotationArtifactID) case "RestApi": var restConfig api.RestAPI if err := s.parser.Parse(params.Data, params.ContentType, &restConfig); err != nil { @@ -212,7 +221,7 @@ func (s *APIDeploymentService) DeployAPIConfiguration(params APIDeploymentParams parsedConfig = restConfig annotationArtifactID = annotationValue(restConfig.Metadata.Annotations, commonconstants.AnnotationArtifactID) default: - return nil, fmt.Errorf("unsupported resource kind %q: must be \"RestApi\" or \"WebSubApi\"", resolvedKind) + return nil, fmt.Errorf("unsupported resource kind %q: must be \"RestApi\", \"WebSubApi\", or \"WebBrokerApi\"", resolvedKind) } // Resolve API ID: explicit param > artifact-id annotation > auto-generate @@ -299,6 +308,15 @@ func (s *APIDeploymentService) DeployAPIConfiguration(params APIDeploymentParams s.logValidationErrors(params.Logger, apiID, apiName, validationErrors) return nil, &ValidationErrorListError{Errors: validationErrors} } + case api.WebBrokerApi: + apiName = c.Spec.DisplayName + apiVersion = c.Spec.Version + // TODO: Add validation for WebBrokerApi once validator supports it + // validationErrors := s.validator.Validate(&c) + // if len(validationErrors) > 0 { + // s.logValidationErrors(params.Logger, apiID, apiName, validationErrors) + // return nil, &ValidationErrorListError{Errors: validationErrors} + // } case api.RestAPI: apiName = c.Spec.DisplayName apiVersion = c.Spec.Version @@ -694,6 +712,33 @@ func resolveVhostSentinels(cfg *any, routerCfg *config.RouterConfig) error { } } *cfg = c + case api.WebBrokerApi: + if c.Spec.Vhosts == nil { + main := routerCfg.VHosts.Main.Default + c.Spec.Vhosts = &struct { + Main string `json:"main" yaml:"main"` + Sandbox *string `json:"sandbox,omitempty" yaml:"sandbox,omitempty"` + }{ + Main: main, + } + if sandboxDefault := routerCfg.VHosts.Sandbox.Default; sandboxDefault != "" { + c.Spec.Vhosts.Sandbox = &sandboxDefault + } + *cfg = c + return nil + } + if c.Spec.Vhosts.Main == constants.VHostGatewayDefault { + c.Spec.Vhosts.Main = routerCfg.VHosts.Main.Default + } + if c.Spec.Vhosts.Sandbox != nil && *c.Spec.Vhosts.Sandbox == constants.VHostGatewayDefault { + resolved := routerCfg.VHosts.Sandbox.Default + if resolved != "" { + c.Spec.Vhosts.Sandbox = &resolved + } else { + c.Spec.Vhosts.Sandbox = nil + } + } + *cfg = c } return nil } From 2e1fbae0c420d2a17435b5a140935efcb5afc51f Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Mon, 11 May 2026 20:11:40 +0530 Subject: [PATCH 02/20] Revamp spec for WebBroker API (Protocol Mediation) --- cli/it/go.mod | 1 + cli/it/go.sum | 3 +- common/go.mod | 10 +- common/go.sum | 15 +- event-gateway/WEBBROKERAPI.md | 701 +++++++++++++++--- event-gateway/build.yaml | 2 + event-gateway/default-policies/map-topic.yaml | 33 + event-gateway/gateway-builder/go.mod | 8 +- event-gateway/gateway-builder/go.sum | 19 +- event-gateway/gateway-runtime/Dockerfile | 1 + event-gateway/gateway-runtime/Makefile | 3 + event-gateway/gateway-runtime/go.mod | 2 +- event-gateway/gateway-runtime/go.sum | 3 +- .../gateway-runtime/internal/binding/types.go | 32 +- .../websocket/broker_api_connector.go | 152 +++- .../internal/connectors/types.go | 10 +- .../gateway-runtime/internal/hub/hub.go | 53 ++ .../internal/hub/policy_adapter.go | 3 + .../internal/runtime/runtime.go | 263 +++++-- .../internal/xdsclient/handler.go | 197 ++++- .../internal/xdsclient/handler_test.go | 10 + event-gateway/policies/map-topic/go.mod | 5 + event-gateway/policies/map-topic/maptopic.go | 70 ++ .../policies/map-topic/policy-definition.yaml | 35 + gateway/gateway-builder/go.mod | 4 +- gateway/gateway-builder/go.sum | 8 +- gateway/gateway-controller/go.mod | 8 +- gateway/gateway-controller/go.sum | 32 +- .../pkg/api/management/webbroker_types.go | 27 +- .../pkg/policyxds/event_channel_translator.go | 87 ++- .../policy-engine/internal/executor/chain.go | 12 +- .../policy-engine/pkg/engine/types.go | 8 + gateway/it/go.mod | 27 +- gateway/it/go.sum | 47 +- go.work | 3 +- go.work.sum | 24 +- kubernetes/gateway-operator/go.mod | 69 +- kubernetes/gateway-operator/go.sum | 149 ++-- platform-api/src/go.mod | 12 +- platform-api/src/go.sum | 18 +- sdk/ai/go.mod | 62 +- sdk/ai/go.sum | 99 +-- tests/mock-servers/mock-platform-api/go.mod | 5 +- tests/mock-servers/mock-platform-api/go.sum | 7 +- 44 files changed, 1751 insertions(+), 588 deletions(-) create mode 100644 event-gateway/default-policies/map-topic.yaml create mode 100644 event-gateway/policies/map-topic/go.mod create mode 100644 event-gateway/policies/map-topic/maptopic.go create mode 100644 event-gateway/policies/map-topic/policy-definition.yaml diff --git a/cli/it/go.mod b/cli/it/go.mod index 10e5c0cc0..f85180eef 100644 --- a/cli/it/go.mod +++ b/cli/it/go.mod @@ -15,5 +15,6 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/spf13/pflag v1.0.10 // indirect ) diff --git a/cli/it/go.sum b/cli/it/go.sum index 3f2d7756e..293ca4c35 100644 --- a/cli/it/go.sum +++ b/cli/it/go.sum @@ -25,8 +25,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -34,6 +34,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= diff --git a/common/go.mod b/common/go.mod index d863e58c9..3e0b41d52 100644 --- a/common/go.mod +++ b/common/go.mod @@ -12,7 +12,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/mattn/go-sqlite3 v1.14.41 github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.47.0 + golang.org/x/crypto v0.48.0 ) require ( @@ -47,10 +47,10 @@ require ( github.com/ugorji/go/codec v1.3.1 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/common/go.sum b/common/go.sum index 5cddad694..7d8be5a72 100644 --- a/common/go.sum +++ b/common/go.sum @@ -107,17 +107,12 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/event-gateway/WEBBROKERAPI.md b/event-gateway/WEBBROKERAPI.md index d4dccdb19..ec11bf4ed 100644 --- a/event-gateway/WEBBROKERAPI.md +++ b/event-gateway/WEBBROKERAPI.md @@ -15,6 +15,7 @@ - [Option 2: Building from Source](#option-2-building-from-source) - [Option 3: Development Mode with Live Reload](#option-3-development-mode-with-live-reload) - [Testing with Policies](#testing-with-policies) +- [End-to-End Testing with Kafka](#end-to-end-testing-with-kafka) - [Implementation Details](#implementation-details) - [Comparison: WebSubApi vs WebBrokerApi](#comparison-websubapi-vs-webbrokerapi) - [Consumer Group Strategy](#consumer-group-strategy) @@ -77,17 +78,18 @@ version: v1.0 context: /base-path receiver: + name: receiver-instance-name # Instance identifier type: websocket # or "sse" in the future properties: {} brokerDriver: + name: broker-instance-name # Instance identifier type: kafka # or "mqtt", "amqp" in the future properties: - topic: default-topic bootstrap.servers: localhost:9092 security.protocol: PLAINTEXT -allChannelPolicies: +policies: # API-level policies applied to all channels on_connection_init: request: - name: policy-name @@ -99,11 +101,29 @@ allChannelPolicies: version: v1 params: {} on_consume: [] + +channels: # Channel-specific configurations with per-channel policies + "/channel-name": + on_connection_init: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-topic-name + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-topic-name ``` ## Example Use Cases -### 1. WebSocket to Kafka with Topic Mapping +### 1. WebSocket to Kafka with Channel Routing ```yaml channels: @@ -112,13 +132,14 @@ channels: version: v1.0 context: /ws-kafka receiver: + name: ws-receiver type: websocket brokerDriver: + name: kafka-driver type: kafka properties: - topic: repo-events bootstrap.servers: localhost:9092 - allChannelPolicies: + policies: # API-level policies on_connection_init: request: - name: api-key-auth @@ -126,28 +147,48 @@ channels: params: in: header name: X-API-Key - on_produce: - - name: map-topics - version: v1 - params: - extraction: - source: header - key: X-Client-Topic - mappings: - client-issues: kafka-repo-issues - client-commits: kafka-repo-commits - defaultTopic: kafka-repo-events + on_produce: [] + on_consume: [] + channels: # Per-channel configurations + "/issues": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-repo-issues + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-repo-issues + "/commits": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-repo-commits + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-repo-commits ``` **Client Usage:** ```javascript +// Connect with channel specified via X-topic header const ws = new WebSocket('ws://localhost:8080/ws-kafka', { headers: { - 'X-API-Key': 'your-api-key' + 'X-API-Key': 'your-api-key', + 'X-topic': '/issues' // Select channel } }); -// Produce to specific topic +// Send message (will produce to kafka-repo-issues) ws.send(JSON.stringify({ headers: { 'X-Client-Topic': 'client-issues' @@ -245,12 +286,14 @@ Each WebSocket connection gets a **unique consumer group** to ensure: Consumer group ID format: `{prefix}-ws-{uuid}` -## Topic Subscription +## Channel Routing and Topic Subscription For WebBrokerApi: -- Consumer subscribes to **all topics** that might be used (from mappings + default) -- Producer publishes to **dynamic topic** determined by policies -- Policies can inspect message metadata to determine target topic +- **Channel Selection**: Client specifies channel via `X-topic` header during WebSocket handshake (e.g., `X-topic: /issues`) +- **Per-Channel Topics**: Each channel defines its own topics via `map-topic` policies with `mode: produceTo` and `mode: consumeFrom` +- **Consumer Subscription**: Each WebSocket connection subscribes only to topics configured for the selected channel +- **Producer Publishing**: Messages are published to the topic specified in the channel's `on_produce` policies +- **Policy Cascade**: API-level policies execute first, followed by channel-specific policies ## Future Enhancements @@ -325,17 +368,18 @@ curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ "version": "v1.0", "context": "/ws-kafka", "receiver": { + "name": "ws-receiver", "type": "websocket", "properties": {} }, "brokerDriver": { + "name": "kafka-driver", "type": "kafka", "properties": { - "topic": "repo-events", "bootstrap.servers": "kafka:29092" } }, - "allChannelPolicies": { + "policies": { "onConnectionInit": { "request": [], "response": [] @@ -343,6 +387,30 @@ curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ "onProduce": [], "onConsume": [] }, + "channels": { + "/issues": { + "on_produce": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "produceTo", + "topic": "repo-events" + } + } + ], + "on_consume": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "consumeFrom", + "topic": "repo-events" + } + } + ] + } + }, "deploymentState": "deployed" } }' @@ -448,7 +516,11 @@ websocat ws://localhost:8081/ws-kafka // test-websocket.js const WebSocket = require('ws'); -const ws = new WebSocket('ws://localhost:8081/ws-kafka'); +const ws = new WebSocket('ws://localhost:8081/ws-kafka', { + headers: { + 'X-topic': '/issues' + } +}); ws.on('open', () => { console.log('Connected to Event Gateway'); @@ -482,7 +554,9 @@ node test-websocket.js **Using Browser Console:** ```javascript -const ws = new WebSocket('ws://localhost:8081/ws-kafka'); +// Note: Browser WebSocket API doesn't support custom headers in constructor +// Use Sec-WebSocket-Protocol or URL query parameters as alternatives +const ws = new WebSocket('ws://localhost:8081/ws-kafka?channel=/issues'); ws.onopen = () => { console.log('Connected!'); @@ -555,18 +629,33 @@ channels: version: v1.0 context: /ws-kafka receiver: + name: ws-receiver type: websocket broker-driver: + name: kafka-driver type: kafka properties: - topic: repo-events bootstrap.servers: localhost:9092 - allChannelPolicies: + policies: on_connection_init: request: [] response: [] on_produce: [] on_consume: [] + channels: + "/issues": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: repo-events + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: repo-events ``` #### 4. Configure Gateway @@ -612,7 +701,7 @@ go run ./cmd/event-gateway \ You should see: ``` INFO Event gateway is ready runtime_id=... -INFO WebBrokerApi WebSocket receiver started channel=websocket-kafka-api context=/ws-kafka topics=[repo-events] +INFO Registered WebBrokerApi binding name=websocket-kafka-api context=/ws-kafka channels=1 topics=[repo-events] ``` #### 6. Test the Connection @@ -620,7 +709,7 @@ INFO WebBrokerApi WebSocket receiver started channel=websocket-kafka-api context Open another terminal and test with websocat: ```bash -websocat ws://localhost:8081/ws-kafka +websocat --header "X-topic: /issues" ws://localhost:8081/ws-kafka ``` Type messages and press Enter to send them to Kafka. @@ -630,7 +719,7 @@ Type messages and press Enter to send them to Kafka. ```bash # View messages being published docker exec -it event-gateway-kafka-1 kafka-console-consumer \ - --bootstrap-server localhost:9092 \ + --bootstrap-server localhost:29092 \ --topic repo-events \ --from-beginning ``` @@ -683,13 +772,14 @@ channels: version: v1.0 context: /secure-ws receiver: + name: ws-receiver type: websocket broker-driver: + name: kafka-driver type: kafka properties: - topic: secure-events bootstrap.servers: kafka:29092 - allChannelPolicies: + policies: on_connection_init: request: - name: api-key-auth @@ -699,49 +789,362 @@ channels: name: X-API-Key on_produce: [] on_consume: [] + channels: + "/secure-channel": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: secure-events + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: secure-events ``` Test with authentication: ```bash # Without API key (should fail) -websocat ws://localhost:8081/secure-ws +websocat --header "X-topic: /secure-channel" ws://localhost:8081/secure-ws # → Connection rejected # With API key (should succeed) -websocat --header "X-API-Key: your-api-key" ws://localhost:8081/secure-ws +websocat --header "X-API-Key: your-api-key" --header "X-topic: /secure-channel" ws://localhost:8081/secure-ws ``` -### Example: Topic Mapping +### Example: Multiple Channels with Different Topics ```yaml -allChannelPolicies: - on_produce: - - name: map-topics - version: v1 - params: - extraction: - source: header - key: X-Target-Topic - mappings: - issues: kafka-repo-issues - commits: kafka-repo-commits - prs: kafka-repo-pull-requests - defaultTopic: kafka-repo-events +channels: + "/issues": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-repo-issues + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-repo-issues + "/commits": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-repo-commits + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-repo-commits ``` -Send messages with topic headers: +Connect to different channels: ```javascript -// In WebSocket message, include metadata for topic routing -ws.send(JSON.stringify({ - headers: { - 'X-Target-Topic': 'issues' - }, - data: { issueId: 123, title: 'Bug fix' } -})); +// Connect to /issues channel +const ws1 = new WebSocket('ws://localhost:8081/ws-kafka', { + headers: { 'X-topic': '/issues' } +}); + +// Connect to /commits channel +const ws2 = new WebSocket('ws://localhost:8081/ws-kafka', { + headers: { 'X-topic': '/commits' } +}); +``` + +## End-to-End Testing with Kafka + +This section demonstrates how to verify the complete WebSocket ↔ Kafka flow in both directions. + +### Prerequisites + +- Docker Compose services running (`docker compose up -d`) +- WebBrokerApi created with separate produce and consume topics (recommended for testing) +- `wscat` installed (`npm install -g wscat`) + +### Example API with Topic Separation + +For clear testing, configure a WebBrokerApi with **separate topics** for produce and consume: + +```bash +curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Basic YWRtaW46YWRtaW4=' \ +--data '{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { "name": "websocket-kafka-api-v1-0" }, + "spec": { + "displayName": "websocket-kafka-api", + "version": "v1.0", + "context": "/ws-kafka", + "receiver": { + "name": "ws-receiver", + "type": "websocket", + "properties": {} + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrap.servers": "kafka:29092" + } + }, + "policies": { + "onConnectionInit": { "request": [], "response": [] }, + "onProduce": [], + "onConsume": [] + }, + "channels": { + "/issues": { + "on_produce": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "produceTo", + "topic": "produce_issues" + } + } + ], + "on_consume": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "consumeFrom", + "topic": "consume_issues" + } + } + ] + } + }, + "deploymentState": "deployed" + } + }' +``` + +**Key Points:** +- `/issues` channel publishes to `produce_issues` topic (one-way) +- `/issues` channel consumes from `consume_issues` topic (one-way) +- **No echo/loopback**: Messages sent via WebSocket don't come back to the sender + +### Test Setup: 3 Terminals + +#### Terminal 1: Kafka Consumer (Monitor WebSocket → Kafka) + +This terminal monitors the `produce_issues` topic to verify messages from WebSocket clients arrive in Kafka: + +```bash +docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-consumer.sh \ + --bootstrap-server localhost:9092 \ + --topic produce_issues \ + --from-beginning +``` + +**Expected Output:** All messages sent via WebSocket will appear here. + +#### Terminal 2: WebSocket Client + +Connect to the `/issues` channel: + +```bash +wscat -c ws://localhost:8081/ws-kafka --header "X-topic: /issues" +``` + +**Expected Output:** +``` +Connected (press CTRL+C to quit) +> +``` + +#### Terminal 3: Kafka Producer (Send to WebSocket Clients) + +This terminal publishes to the `consume_issues` topic, which will be delivered to WebSocket clients: + +```bash +docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-producer.sh \ + --bootstrap-server localhost:9092 \ + --topic consume_issues +``` + +**Expected Output:** +``` +> +``` + +### Test 1: WebSocket → Kafka (Produce Path) + +**Objective:** Verify messages sent from WebSocket client arrive in Kafka. + +**Steps:** + +1. In **Terminal 2** (WebSocket client), type a message and press Enter: + ``` + > {"message": "Hello from WebSocket", "timestamp": "2026-05-11T12:00:00Z"} + ``` + +2. In **Terminal 1** (Kafka consumer), verify the message appears: + ``` + {"message": "Hello from WebSocket", "timestamp": "2026-05-11T12:00:00Z"} + ``` + +3. Send multiple messages to test throughput: + ``` + > {"event": "issue_created", "id": 1} + > {"event": "issue_updated", "id": 1, "status": "in-progress"} + > {"event": "issue_closed", "id": 1} + ``` + +4. Verify all messages appear in **Terminal 1** in order. + +**✅ Success Criteria:** +- All messages from WebSocket appear in the `produce_issues` Kafka topic +- Messages maintain order +- No messages echo back to WebSocket client (Terminal 2 shows only `>` prompt) + +### Test 2: Kafka → WebSocket (Consume Path) + +**Objective:** Verify messages published to Kafka are delivered to WebSocket clients. + +**Steps:** + +1. In **Terminal 3** (Kafka producer), type a message and press Enter: + ``` + > {"message": "Hello from Kafka", "source": "external-service"} + ``` + +2. In **Terminal 2** (WebSocket client), verify the message is received: + ``` + < {"message": "Hello from Kafka", "source": "external-service"} + ``` + +3. Send multiple messages from Kafka: + ``` + > {"event": "notification", "type": "email", "status": "sent"} + > {"event": "notification", "type": "sms", "status": "delivered"} + ``` + +4. Verify all messages appear in **Terminal 2** immediately. + +**✅ Success Criteria:** +- All messages from `consume_issues` Kafka topic appear in WebSocket client +- Messages are delivered in real-time (< 1 second delay) +- Messages maintain order + +### Test 3: Multiple WebSocket Connections + +**Objective:** Verify each connection receives messages independently. + +**Steps:** + +1. Open **Terminal 4** with a second WebSocket client: + ```bash + wscat -c ws://localhost:8081/ws-kafka --header "X-topic: /issues" + ``` + +2. In **Terminal 3** (Kafka producer), send a message: + ``` + > {"broadcast": "message to all clients"} + ``` + +3. Verify the message appears in **both Terminal 2 and Terminal 4**. + +4. In **Terminal 2**, send a message: + ``` + > {"from": "client1"} + ``` + +5. Verify in **Terminal 1** (Kafka consumer) that the message appears. + +6. Verify in **Terminal 2 and Terminal 4** that the message does NOT echo back. + +**✅ Success Criteria:** +- Each WebSocket connection has its own unique consumer group +- All connections receive broadcast messages from Kafka +- Messages sent by one client don't echo to any WebSocket client +- Each connection can produce independently + +### Test 4: Verify Topic Isolation + +**Objective:** Confirm produce and consume topics are completely isolated. + +**Steps:** + +1. List Kafka topics to verify both exist: + ```bash + docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-topics.sh \ + --bootstrap-server localhost:9092 \ + --list | grep issues + ``` + + **Expected Output:** + ``` + consume_issues + produce_issues + ``` + +2. Check message count in `produce_issues`: + ```bash + docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-run-class.sh \ + kafka.tools.GetOffsetShell \ + --broker-list localhost:9092 \ + --topic produce_issues + ``` + +3. Check message count in `consume_issues`: + ```bash + docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-run-class.sh \ + kafka.tools.GetOffsetShell \ + --broker-list localhost:9092 \ + --topic consume_issues + ``` + +4. Verify counts match your test messages (produce_issues has WebSocket messages, consume_issues has Kafka messages). + +**✅ Success Criteria:** +- Topics are independent with different message counts +- No cross-contamination between produce and consume paths + +### Debugging Tips + +**Check Event Gateway Logs:** +```bash +docker compose logs -f event-gateway | grep -E "Map Topic|Publishing|received from WebSocket" +``` + +**Verify Channel Configuration:** +```bash +docker compose logs event-gateway | grep "Built policy chains for WebBrokerApi channel" +``` + +**Expected Log Output:** +``` +level=INFO msg="Built policy chains for WebBrokerApi channel" api=websocket-kafka-api-v1-0 channel=/issues topics="[produce_issues consume_issues]" +``` + +**Check WebSocket Connection:** +```bash +docker compose logs event-gateway | grep "WebSocket handshake completed" ``` +**Expected Log Output:** +``` +level=INFO msg="[4] WebSocket handshake completed" connID=xxx api=websocket-kafka-api-v1-0 channel=/issues topics=[consume_issues] +``` + +Note the `topics=[consume_issues]` - this confirms the consumer only subscribes to the consume topic, not the produce topic. + ## Troubleshooting **WebSocket endpoint not available (Empty reply from server):** @@ -760,8 +1163,10 @@ ws.send(JSON.stringify({ - In static file mode, verify `channels.yaml` has the WebBrokerApi entry and `controlplane.enabled = false` **Connection rejected during handshake:** +- **Missing X-topic header**: Verify the `X-topic` header is included with a valid channel name (e.g., `X-topic: /issues`) - Check `on_connection_init.request` policies (e.g., API key validation) - Verify headers are correctly set in upgrade request +- Check logs for: "Missing X-topic header" or "Unknown channel in X-topic header" **Messages not reaching Kafka:** - Check `on_produce` policies for short-circuit conditions @@ -899,18 +1304,33 @@ channels: version: v1.0 context: /simple receiver: + name: ws-receiver type: websocket broker-driver: + name: kafka-driver type: kafka properties: - topic: simple-events bootstrap.servers: kafka:29092 - allChannelPolicies: + policies: on_connection_init: request: [] response: [] on_produce: [] on_consume: [] + channels: + "/default": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: simple-events + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: simple-events ``` **With Authentication:** @@ -922,13 +1342,14 @@ channels: version: v1.0 context: /secure receiver: + name: ws-receiver type: websocket broker-driver: + name: kafka-driver type: kafka properties: - topic: secure-events bootstrap.servers: kafka:29092 - allChannelPolicies: + policies: on_connection_init: request: - name: api-key-auth @@ -938,9 +1359,23 @@ channels: name: X-API-Key on_produce: [] on_consume: [] + channels: + "/secure-channel": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: secure-events + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: secure-events ``` -**With Topic Mapping and Transformation:** +**With Multiple Channels:** ```yaml channels: @@ -949,29 +1384,19 @@ channels: version: v1.0 context: /smart receiver: + name: ws-receiver type: websocket broker-driver: + name: kafka-driver type: kafka properties: - topic: default-events bootstrap.servers: kafka:29092 - allChannelPolicies: + policies: on_connection_init: request: - name: api-key-auth version: v1 - on_produce: - - name: map-topics - version: v1 - params: - extraction: - source: header - key: X-Event-Type - mappings: - user.created: users-topic - user.updated: users-topic - order.created: orders-topic - defaultTopic: default-events + on_produce: [] on_consume: - name: set-headers version: v1 @@ -979,6 +1404,33 @@ channels: headers: X-Gateway: event-gateway X-Timestamp: "${timestamp}" + channels: + "/users": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: users-topic + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: users-topic + "/orders": + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: orders-topic + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: orders-topic ``` ### Useful Tools @@ -990,13 +1442,13 @@ brew install websocat # macOS cargo install websocat # Linux/Rust # Basic usage -websocat ws://localhost:8081/ws-kafka +websocat --header "X-topic: /channel-name" ws://localhost:8081/ws-kafka # With headers -websocat --header "X-API-Key: test" ws://localhost:8081/secure +websocat --header "X-API-Key: test" --header "X-topic: /secure-channel" ws://localhost:8081/secure # With text protocol -websocat -t ws://localhost:8081/ws-kafka +websocat -t --header "X-topic: /channel-name" ws://localhost:8081/ws-kafka ``` **wscat** - Alternative WebSocket CLI: @@ -1005,10 +1457,10 @@ websocat -t ws://localhost:8081/ws-kafka npm install -g wscat # Connect -wscat -c ws://localhost:8081/ws-kafka +wscat -c ws://localhost:8081/ws-kafka -H "X-topic: /channel-name" # With headers -wscat -c ws://localhost:8081/secure -H "X-API-Key: test" +wscat -c ws://localhost:8081/secure -H "X-API-Key: test" -H "X-topic: /secure-channel" ``` **kcat (kafkacat)** - Kafka CLI tool: @@ -1039,7 +1491,7 @@ echo "test message" | kcat -b localhost:9092 -t repo-events -P "context": "/websocket-kafka", "receiver": { - "name": "my-websocket-client", + "name": "my-websocket-receiver", "type": "websocket", "properties": {} }, @@ -1048,14 +1500,13 @@ echo "test message" | kcat -b localhost:9092 -t repo-events -P "name": "my-kafka-broker", "type": "kafka", "properties": { - "topic": "repo-events", "bootstrap.servers": "localhost:9092", "security.protocol": "PLAINTEXT" } }, - "allChannelPolicies": { - "on_connection_init": { + "policies": { + "onConnectionInit": { "request": [ { "name": "api-key-auth", @@ -1068,25 +1519,59 @@ echo "test message" | kcat -b localhost:9092 -t repo-events -P ], "response": [] }, - "on_produce": [ - { - "name": "map-topics", - "version": "v1", - "params": { - "extraction": { - "source": "header", - "key": "X-Client-Topic" - }, - "mappings": { - "client-issues-topic": "kafka-repo-issues", - "client-commits-topic": "kafka-repo-commits", - "client-pr-topic": "kafka-repo-pull-requests" - }, - "defaultTopic": "kafka-repo-events" + "onProduce": [], + "onConsume": [] + }, + + "channels": { + "/issues": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "produceTo", + "topic": "kafka-repo-issues" + } + } + ], + "on_consume": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "consumeFrom", + "topic": "kafka-repo-issues" + } + } + ] + }, + "/commits": { + "on_produce": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "produceTo", + "topic": "kafka-repo-commits" + } } - } - ], - "on_consume": [] + ], + "on_consume": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "consumeFrom", + "topic": "kafka-repo-commits" + } + } + ] + } }, "deploymentState": "deployed" @@ -1094,6 +1579,16 @@ echo "test message" | kcat -b localhost:9092 -t repo-events -P } ``` +### Key Changes in New Spec + +1. **Receiver and BrokerDriver Names**: Added `name` field to both `receiver` and `brokerDriver` for instance identification +2. **API-Level Policies**: Renamed `allChannelPolicies` → `policies` for API-level policy enforcement +3. **Channel-Specific Configs**: New `channels` map where each key is a channel name (e.g., `/issues`, `/commits`) +4. **Per-Channel Policies**: Each channel has its own `on_connection_init`, `on_produce`, and `on_consume` policies +5. **Topic Extraction**: Topics no longer in `brokerDriver.properties.topic`; instead extracted from `map-topic` policies with `mode: produceTo` and `mode: consumeFrom` +6. **Channel Routing**: Client selects channel via `X-topic` header during WebSocket handshake +7. **Policy Cascade**: API-level policies execute first, then channel-specific policies + ## Next Steps - Review [WebSub documentation](README.md) for comparison diff --git a/event-gateway/build.yaml b/event-gateway/build.yaml index dc6a43070..6efb4c649 100644 --- a/event-gateway/build.yaml +++ b/event-gateway/build.yaml @@ -6,6 +6,8 @@ policies: gomodule: github.com/wso2/gateway-controllers/policies/set-headers@v1.0.1 - name: api-key-auth gomodule: github.com/wso2/gateway-controllers/policies/api-key-auth@v1.0.1 + - name: map-topic + filePath: ./policies/map-topic - name: advanced-ratelimit gomodule: github.com/wso2/gateway-controllers/policies/advanced-ratelimit@v1 - name: analytics-header-filter diff --git a/event-gateway/default-policies/map-topic.yaml b/event-gateway/default-policies/map-topic.yaml new file mode 100644 index 000000000..88f543127 --- /dev/null +++ b/event-gateway/default-policies/map-topic.yaml @@ -0,0 +1,33 @@ +# Map-topic policy for WebBrokerApi channel topic configuration +name: map-topic +version: v1.0.0 +description: | + Configures the Kafka topic for producing to or consuming from in WebBrokerApi channels. + This policy specifies the exact topic name at the broker level. + +parameters: + type: object + additionalProperties: false + properties: + mode: + type: string + x-wso2-policy-advanced-param: false + description: | + Specifies the operation mode: "produceTo" for producing messages to a topic, + or "consumeFrom" for consuming messages from a topic. + enum: + - produceTo + - consumeFrom + + topic: + type: string + x-wso2-policy-advanced-param: false + description: | + The Kafka topic name to produce to or consume from. + minLength: 1 + maxLength: 256 + pattern: "^[a-zA-Z0-9._-]+$" + + required: + - mode + - topic diff --git a/event-gateway/gateway-builder/go.mod b/event-gateway/gateway-builder/go.mod index 13526c876..9ea1dc02b 100644 --- a/event-gateway/gateway-builder/go.mod +++ b/event-gateway/gateway-builder/go.mod @@ -3,6 +3,12 @@ module github.com/wso2/api-platform/event-gateway/gateway-builder go 1.24.5 require ( - golang.org/x/mod v0.32.0 + golang.org/x/mod v0.33.0 gopkg.in/yaml.v3 v3.0.1 ) + +require ( + github.com/kr/pretty v0.3.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) diff --git a/event-gateway/gateway-builder/go.sum b/event-gateway/gateway-builder/go.sum index 3bf5aa33b..4a0f66cbe 100644 --- a/event-gateway/gateway-builder/go.sum +++ b/event-gateway/gateway-builder/go.sum @@ -1,6 +1,19 @@ -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/event-gateway/gateway-runtime/Dockerfile b/event-gateway/gateway-runtime/Dockerfile index 50a716d40..5691749db 100644 --- a/event-gateway/gateway-runtime/Dockerfile +++ b/event-gateway/gateway-runtime/Dockerfile @@ -86,6 +86,7 @@ COPY --from=sdk-core . /api-platform/sdk/core COPY --from=common . /api-platform/common COPY --from=analytics . /api-platform/gateway/system-policies/analytics COPY --from=policy-engine . /api-platform/gateway/gateway-runtime/policy-engine +COPY --from=policies . /api-platform/event-gateway/policies COPY --from=target build.yaml /api-platform/event-gateway/build.yaml RUN mkdir -p /api-platform/output diff --git a/event-gateway/gateway-runtime/Makefile b/event-gateway/gateway-runtime/Makefile index e98eb8e30..5a8bd17f1 100644 --- a/event-gateway/gateway-runtime/Makefile +++ b/event-gateway/gateway-runtime/Makefile @@ -89,6 +89,7 @@ build-docker: ## Build Docker image --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ + --build-context policies=../policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ @@ -109,6 +110,7 @@ build-debug: ## Build Docker image with Delve remote debugger (port 2345) --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ + --build-context policies=../policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ @@ -129,6 +131,7 @@ build-and-push-multiarch: ## Build and push Docker image for multiple architectu --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ + --build-context policies=../policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ diff --git a/event-gateway/gateway-runtime/go.mod b/event-gateway/gateway-runtime/go.mod index fe2f29b4d..0abf299ae 100644 --- a/event-gateway/gateway-runtime/go.mod +++ b/event-gateway/gateway-runtime/go.mod @@ -5,7 +5,7 @@ go 1.26.2 require ( github.com/envoyproxy/go-control-plane/envoy v1.36.0 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/knadh/koanf/parsers/toml/v2 v2.2.0 github.com/knadh/koanf/providers/env v1.1.0 github.com/knadh/koanf/providers/file v1.2.1 diff --git a/event-gateway/gateway-runtime/go.sum b/event-gateway/gateway-runtime/go.sum index 77b917475..e5119325a 100644 --- a/event-gateway/gateway-runtime/go.sum +++ b/event-gateway/gateway-runtime/go.sum @@ -32,8 +32,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= diff --git a/event-gateway/gateway-runtime/internal/binding/types.go b/event-gateway/gateway-runtime/internal/binding/types.go index 176c97037..350a37561 100644 --- a/event-gateway/gateway-runtime/internal/binding/types.go +++ b/event-gateway/gateway-runtime/internal/binding/types.go @@ -64,15 +64,23 @@ type ChannelDef struct { // It provides bidirectional streaming between web-friendly protocols (WebSocket, SSE) // and message brokers (Kafka, MQTT) with per-connection isolation. type WebBrokerApiBinding struct { - Kind string `yaml:"kind"` // "WebBrokerApi" - APIID string `yaml:"apiId"` - Name string `yaml:"name"` - Version string `yaml:"version"` - Context string `yaml:"context"` - Vhost string `yaml:"vhost"` - Receiver ReceiverSpec `yaml:"receiver"` - BrokerDriver BrokerDriverSpec `yaml:"broker-driver"` - Policies ProtocolMediationPolicies `yaml:"allChannelPolicies"` + Kind string `yaml:"kind"` // "WebBrokerApi" + APIID string `yaml:"apiId"` + Name string `yaml:"name"` + Version string `yaml:"version"` + Context string `yaml:"context"` + Vhost string `yaml:"vhost"` + Receiver ReceiverSpec `yaml:"receiver"` + BrokerDriver BrokerDriverSpec `yaml:"broker-driver"` + Policies ProtocolMediationPolicies `yaml:"policies"` // API-level policies + Channels map[string]WebBrokerChannelDef `yaml:"channels,omitempty"` // Channel-specific policies +} + +// WebBrokerChannelDef defines a single channel within a WebBrokerApi with its policies. +type WebBrokerChannelDef struct { + OnConnectionInit ConnectionInitPolicies `yaml:"on_connection_init"` + OnProduce []PolicyRef `yaml:"on_produce"` + OnConsume []PolicyRef `yaml:"on_consume"` } // ProtocolMediationPolicies defines policy enforcement points for protocol mediation. @@ -90,7 +98,8 @@ type ConnectionInitPolicies struct { // ReceiverSpec defines the receiver connector type and configuration. type ReceiverSpec struct { - Type string `yaml:"type"` // "websub", "websocket", or "sse" + Name string `yaml:"name,omitempty"` // Receiver instance name (for WebBrokerApi) + Type string `yaml:"type"` // "websub", "websocket", or "sse" Path string `yaml:"path"` Backpressure string `yaml:"backpressure"` // "drop-oldest", "block", "close" Properties map[string]interface{} `yaml:"properties"` @@ -98,7 +107,8 @@ type ReceiverSpec struct { // BrokerDriverSpec defines the broker-driver connector type and configuration. type BrokerDriverSpec struct { - Type string `yaml:"type"` // "kafka" + Name string `yaml:"name,omitempty"` // Broker driver instance name (for WebBrokerApi) + Type string `yaml:"type"` // "kafka" Topic string `yaml:"topic"` Ordering string `yaml:"ordering"` // "ordered" or "unordered" Config map[string]interface{} `yaml:"config"` // broker-driver-specific config (e.g. brokers, tls) diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go index 03cf253e6..cde0ab5b6 100644 --- a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -55,14 +55,17 @@ type WebBrokerApiReceiver struct { // brokerApiConnection represents a single WebSocket connection with bidirectional channels. type brokerApiConnection struct { - connID string - ws *websocket.Conn - inbound chan *connectors.Message // client → broker - outbound chan *connectors.Message // broker → client - kafkaConsumer connectors.Receiver - cancel context.CancelFunc - closed bool - mu sync.Mutex + connID string + ws *websocket.Conn + inbound chan *connectors.Message // client → broker + outbound chan *connectors.Message // broker → client + kafkaConsumer connectors.Receiver + cancel context.CancelFunc + closed bool + mu sync.Mutex + channelName string // Selected channel from X-topic header + produceChainKey string // Policy chain key for on_produce + consumeChainKey string // Policy chain key for on_consume } // NewBrokerApiReceiver creates a WebSocket receiver for WebBrokerApi protocol mediation. @@ -135,17 +138,57 @@ func (e *WebBrokerApiReceiver) Stop(ctx context.Context) error { func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Request) { // TODO: Change detailed flow logs ([1]-[8]) to debug level before production deployment // These Info-level logs are useful for development/testing but should be Debug in production + // Extract channel name from X-topic header. + xTopicHeader := r.Header.Get("X-topic") + channelName := xTopicHeader + if channelName == "" { + slog.Error("Missing X-topic header in WebSocket connection", "api", e.channel.Name, "remote", r.RemoteAddr) + http.Error(w, "Missing X-topic header", http.StatusBadRequest) + return + } + + // Validate channel exists in metadata. + channelNamesIface, ok := e.channel.Metadata["channelNames"] + if !ok { + slog.Error("Missing channelNames in metadata", "api", e.channel.Name, "remote", r.RemoteAddr) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + channelNames, ok := channelNamesIface.([]string) + if !ok { + slog.Error("Invalid channelNames metadata type", "api", e.channel.Name, "remote", r.RemoteAddr) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + channelExists := false + for _, ch := range channelNames { + if ch == channelName { + channelExists = true + break + } + } + + if !channelExists { + slog.Error("Unknown channel in X-topic header", "api", e.channel.Name, "channel", channelName, "remote", r.RemoteAddr) + http.Error(w, fmt.Sprintf("Unknown channel: %s", channelName), http.StatusNotFound) + return + } + slog.Info("[1] WebSocket connection attempted", - "channel", e.channel.Name, + "api", e.channel.Name, + "channel", channelName, "method", r.Method, "path", r.URL.Path, "remote_addr", r.RemoteAddr, "upgrade_header", r.Header.Get("Upgrade"), "connection_header", r.Header.Get("Connection")) - // Apply on_connection_init.request policies. - slog.Info("[2] Applying onConnectionInit.request policies", - "channel", e.channel.Name, + // Apply API-level on_connection_init.request policies. + slog.Info("[2] Applying API-level onConnectionInit.request policies", + "api", e.channel.Name, + "channel", channelName, "remote_addr", r.RemoteAddr) msg := &connectors.Message{ @@ -187,6 +230,10 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ } } + // Apply channel-specific on_connection_init.request policies. + // TODO: Implement channel-specific policy application here. + // For now, only API-level policies are applied. + // Upgrade to WebSocket. ws, err := upgrader.Upgrade(w, r, nil) if err != nil { @@ -194,8 +241,8 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ return } - // Apply on_connection_init.response policies (if needed). - slog.Info("[3] Applying onConnectionInit.response policies", "channel", e.channel.Name) + // Apply API-level on_connection_init.response policies. + slog.Info("[3] Applying API-level onConnectionInit.response policies", "api", e.channel.Name, "channel", channelName) respMsg := &connectors.Message{ Headers: map[string][]string{}, @@ -206,21 +253,49 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ return } + // Extract channel-specific policy chain keys from metadata. + var produceChainKey, consumeChainKey string + if channelChainsIface, ok := e.channel.Metadata["channelChains"]; ok { + // channelChains is stored as map[string]map[string]string + if channelChainsMap, ok := channelChainsIface.(map[string]map[string]string); ok { + if chainData, ok := channelChainsMap[channelName]; ok { + produceChainKey = chainData["ProduceKey"] + consumeChainKey = chainData["ConsumeKey"] + } + } + } + + // Extract topics for this channel. + topicToChannelIface, _ := e.channel.Metadata["topicToChannel"] + topicToChannel, _ := topicToChannelIface.(map[string]string) + channelTopics := []string{} + for topic, ch := range topicToChannel { + if ch == channelName { + channelTopics = append(channelTopics, topic) + } + } + if len(channelTopics) == 0 { + slog.Warn("No topics found for channel", "api", e.channel.Name, "channel", channelName) + } + // Create per-connection resources. connID := uuid.New().String() ctx, cancel := context.WithCancel(e.ctx) conn := &brokerApiConnection{ - connID: connID, - ws: ws, - inbound: make(chan *connectors.Message, 256), - outbound: make(chan *connectors.Message, 256), - cancel: cancel, + connID: connID, + ws: ws, + inbound: make(chan *connectors.Message, 256), + outbound: make(chan *connectors.Message, 256), + cancel: cancel, + channelName: channelName, + produceChainKey: produceChainKey, + consumeChainKey: consumeChainKey, } // Create unique consumer group for this connection. groupID := fmt.Sprintf("%s-ws-%s", e.opts.ConsumerGroupPrefix, connID) - consumer, err := e.brokerDriver.Subscribe(groupID, e.opts.Topics, func(ctx context.Context, msg *connectors.Message) error { + consumer, err := e.brokerDriver.Subscribe(groupID, channelTopics, func(ctx context.Context, msg *connectors.Message) error { // Kafka message received → outbound channel. select { case conn.outbound <- msg: @@ -252,7 +327,7 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ e.connections[connID] = conn e.mu.Unlock() - slog.Info("[4] WebSocket handshake completed", "connID", connID, "channel", e.channel.Name, "remote", ws.RemoteAddr(), "consumer_group", groupID) + slog.Info("[4] WebSocket handshake completed", "connID", connID, "api", e.channel.Name, "channel", channelName, "remote", ws.RemoteAddr(), "consumer_group", groupID, "topics", channelTopics) // Start goroutines for bidirectional communication. go e.inboundLoop(ctx, conn) @@ -285,7 +360,8 @@ func (e *WebBrokerApiReceiver) readLoop(ctx context.Context, conn *brokerApiConn slog.Info("[5] Message received from WebSocket client", "connID", conn.connID, - "channel", e.channel.Name, + "api", e.channel.Name, + "channel", conn.channelName, "size_bytes", len(data)) // Extract headers from WebSocket message (if any). @@ -312,19 +388,22 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC case <-ctx.Done(): return case msg := <-conn.inbound: - // Apply on_produce policies. - slog.Info("[5] Applying onProduce policies", + // Apply channel-specific on_produce policies. + slog.Info("[5] Applying channel onProduce policies", "connID", conn.connID, - "channel", e.channel.Name, + "api", e.channel.Name, + "channel", conn.channelName, + "chain_key", conn.produceChainKey, "message_size", len(msg.Value)) - processed, shortCircuited, err := e.processor.ProcessProduce(ctx, e.channel.Name, msg) + // Use channel-specific policy chain key via ProcessByChainKey + processed, shortCircuited, err := e.processor.ProcessByChainKey(ctx, e.channel.Name, conn.produceChainKey, msg) if err != nil { - slog.Error("[5] onProduce policy failed", "connID", conn.connID, "channel", e.channel.Name, "error", err) + slog.Error("[5] onProduce policy failed", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName, "error", err) continue } if shortCircuited { - slog.Info("[5] Message dropped by onProduce policy", "connID", conn.connID, "channel", e.channel.Name) + slog.Info("[5] Message dropped by onProduce policy", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName) continue } @@ -344,7 +423,8 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC // Publish to Kafka. slog.Info("[6] Publishing message to Kafka", "connID", conn.connID, - "channel", e.channel.Name, + "api", e.channel.Name, + "channel", conn.channelName, "topic", targetTopic, "message_size", len(processed.Value)) @@ -364,19 +444,21 @@ func (e *WebBrokerApiReceiver) outboundLoop(ctx context.Context, conn *brokerApi case <-ctx.Done(): return case msg := <-conn.outbound: - slog.Info("[7] Applying onConsume policies", + slog.Info("[7] Applying channel onConsume policies", "connID", conn.connID, - "channel", e.channel.Name, + "api", e.channel.Name, + "channel", conn.channelName, + "chain_key", conn.consumeChainKey, "message_size", len(msg.Value)) - // Apply on_consume policies. - processed, shortCircuited, err := e.processor.ProcessConsume(ctx, e.channel.Name, msg) + // Use channel-specific policy chain key via ProcessByChainKey + processed, shortCircuited, err := e.processor.ProcessByChainKey(ctx, e.channel.Name, conn.consumeChainKey, msg) if err != nil { - slog.Error("[7] onConsume policy failed", "connID", conn.connID, "channel", e.channel.Name, "error", err) + slog.Error("[7] onConsume policy failed", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName, "error", err) continue } if shortCircuited { - slog.Info("[7] Message dropped by onConsume policy", "connID", conn.connID, "channel", e.channel.Name) + slog.Info("[7] Message dropped by onConsume policy", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName) continue } diff --git a/event-gateway/gateway-runtime/internal/connectors/types.go b/event-gateway/gateway-runtime/internal/connectors/types.go index bb1ade6f2..871fa5a1f 100644 --- a/event-gateway/gateway-runtime/internal/connectors/types.go +++ b/event-gateway/gateway-runtime/internal/connectors/types.go @@ -53,6 +53,9 @@ type MessageProcessor interface { ProcessConnectionInitResponse(ctx context.Context, bindingName string, msg *Message) (*Message, error) ProcessProduce(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) ProcessConsume(ctx context.Context, bindingName string, msg *Message) (*Message, bool, error) + + // Execute policies using a specific chain key (for channel-specific policies) + ProcessByChainKey(ctx context.Context, bindingName string, chainKey string, msg *Message) (*Message, bool, error) } // BrokerDriver manages connections to a backend event system (e.g. Kafka, NATS). @@ -76,9 +79,10 @@ type ChannelInfo struct { PublicTopic string BrokerDriverTopic string Ordering string - Channels map[string]string // channel-name → Kafka topic (WebSubApi only) - InternalSubTopic string // internal subscription sync topic (WebSubApi only) - Topics []string // topics to subscribe to (WebBrokerApi only) + Channels map[string]string // channel-name → Kafka topic (WebSubApi only) + InternalSubTopic string // internal subscription sync topic (WebSubApi only) + Topics []string // topics to subscribe to (WebBrokerApi only) + Metadata map[string]interface{} // additional metadata (e.g., channelChains, topicToChannel) } // RouteMux is an HTTP request multiplexer that supports dynamic route registration. diff --git a/event-gateway/gateway-runtime/internal/hub/hub.go b/event-gateway/gateway-runtime/internal/hub/hub.go index 65d675179..6f12f77f6 100644 --- a/event-gateway/gateway-runtime/internal/hub/hub.go +++ b/event-gateway/gateway-runtime/internal/hub/hub.go @@ -480,3 +480,56 @@ func (h *Hub) ProcessConsume(ctx context.Context, bindingName string, msg *conne // Reuse ProcessOutbound for on_consume policies return h.ProcessOutbound(ctx, bindingName, msg) } + +// ProcessByChainKey executes policies using a specific chain key directly. +// This is used for WebBrokerApi channel-specific policies where we know the exact chain key to use. +// Returns the (possibly mutated) message and whether it was short-circuited. +func (h *Hub) ProcessByChainKey(ctx context.Context, bindingName string, chainKey string, msg *connectors.Message) (*connectors.Message, bool, error) { + if chainKey == "" { + // No chain to execute + return msg, false, nil + } + + // Get binding for metadata (API context, version, etc.) + binding := h.GetBinding(bindingName) + if binding == nil { + return nil, false, fmt.Errorf("binding not found: %s", bindingName) + } + + chain := h.engine.GetChain(chainKey) + if chain == nil { + // Chain key not found - this might be OK if the chain has no policies + return msg, false, nil + } + + // Execute header policies + reqHeaderCtx := MessageToRequestHeaderContext(msg, binding) + result, err := h.engine.ExecuteRequestHeaderPolicies(ctx, chainKey, reqHeaderCtx.SharedContext, reqHeaderCtx) + if err != nil { + return nil, false, fmt.Errorf("policy header execution failed: %w", err) + } + if result.ShortCircuited { + logShortCircuit("Message short-circuited by policy", bindingName, chainKey, result.ImmediateResponse) + return immediateResponseToMessage(result.ImmediateResponse), true, nil + } + if err := ApplyRequestHeaderResult(result, msg); err != nil { + return nil, false, fmt.Errorf("failed to apply header result: %w", err) + } + + // Execute body policies if chain requires them + if chain.RequiresRequestBody { + reqCtx := MessageToRequestContext(msg, binding) + bodyResult, err := h.engine.ExecuteRequestBodyPolicies(ctx, chainKey, reqCtx.SharedContext, reqCtx) + if err != nil { + return nil, false, fmt.Errorf("policy body execution failed: %w", err) + } + if bodyResult.ShortCircuited { + return immediateResponseToMessage(bodyResult.ImmediateResponse), true, nil + } + if err := ApplyRequestBodyResult(bodyResult, msg); err != nil { + return nil, false, fmt.Errorf("failed to apply body result: %w", err) + } + } + + return msg, false, nil +} diff --git a/event-gateway/gateway-runtime/internal/hub/policy_adapter.go b/event-gateway/gateway-runtime/internal/hub/policy_adapter.go index 746cf628f..cf3dee8e7 100644 --- a/event-gateway/gateway-runtime/internal/hub/policy_adapter.go +++ b/event-gateway/gateway-runtime/internal/hub/policy_adapter.go @@ -216,6 +216,9 @@ func ApplyRequestBodyResult(result *engine.RequestBodyResult, msg *connectors.Me if result.Body != nil { msg.Value = result.Body } + if result.Topic != "" { + msg.Topic = result.Topic + } return nil } diff --git a/event-gateway/gateway-runtime/internal/runtime/runtime.go b/event-gateway/gateway-runtime/internal/runtime/runtime.go index f20b7fd50..5b58891fe 100644 --- a/event-gateway/gateway-runtime/internal/runtime/runtime.go +++ b/event-gateway/gateway-runtime/internal/runtime/runtime.go @@ -294,30 +294,39 @@ func (r *Runtime) LoadChannels(channelsPath string) error { for _, wbb := range parseResult.WebBrokerApiBindings { vhost := defaultVhost(wbb.Vhost) - // Build policy chains for protocol mediation. - connInitReqKey, _, produceKey, consumeKey, err := r.buildWebBrokerApiPolicyChains(wbb, vhost) + // Build API-level policy chains. + apiConnInitReqKey, _, _, _, err := r.buildWebBrokerApiPolicyChains(wbb, vhost, "") if err != nil { - return fmt.Errorf("failed to build chains for WebBrokerApi %q: %w", wbb.Name, err) + return fmt.Errorf("failed to build API-level chains for WebBrokerApi %q: %w", wbb.Name, err) } - // Extract topics from broker driver properties. - var topics []string - brokerDriverConfig := wbb.BrokerDriver.Config - if brokerDriverConfig == nil { - brokerDriverConfig = wbb.BrokerDriver.Properties - } + // Build per-channel policy chains and collect topics. + channelChains := make(map[string]ChannelPolicyChains) + allTopics := []string{} // All topics (produce + consume) for ensuring they exist + topicToChannel := make(map[string]string) // Only consume topics for subscription mapping - // Get default topic from BrokerDriver.Topic or properties - defaultTopic := wbb.BrokerDriver.Topic - if defaultTopic == "" { - if topicVal, ok := brokerDriverConfig["topic"]; ok { - if topicStr, ok := topicVal.(string); ok { - defaultTopic = topicStr - } + for channelName, channelDef := range wbb.Channels { + connInitReqKey, connInitRespKey, produceKey, consumeKey, err := r.buildWebBrokerApiPolicyChains(wbb, vhost, channelName) + if err != nil { + return fmt.Errorf("failed to build chains for channel %q in WebBrokerApi %q: %w", channelName, wbb.Name, err) + } + + channelChains[channelName] = ChannelPolicyChains{ + ConnInitReqKey: connInitReqKey, + ConnInitRespKey: connInitRespKey, + ProduceKey: produceKey, + ConsumeKey: consumeKey, + } + + // Extract ALL topics (produce + consume) to ensure they exist in Kafka + allChannelTopics := extractAllTopicsFromChannelPolicies(channelDef) + allTopics = append(allTopics, allChannelTopics...) + + // Extract ONLY consume topics for subscription mapping + consumeTopics := extractTopicsFromChannelPolicies(channelDef) + for _, topic := range consumeTopics { + topicToChannel[topic] = channelName } - } - if defaultTopic != "" { - topics = append(topics, defaultTopic) } // Register binding in hub. @@ -328,9 +337,9 @@ func (r *Runtime) LoadChannels(channelsPath string) error { Context: wbb.Context, Version: wbb.Version, Vhost: vhost, - SubscribeChainKey: connInitReqKey, // on_connection_init.request - InboundChainKey: produceKey, // on_produce - OutboundChainKey: consumeKey, // on_consume + SubscribeChainKey: apiConnInitReqKey, + InboundChainKey: "", // Determined per-channel + OutboundChainKey: "", // Determined per-channel }) // Create broker-driver. @@ -338,6 +347,10 @@ func (r *Runtime) LoadChannels(channelsPath string) error { if brokerDriverType == "" { brokerDriverType = "kafka" } + brokerDriverConfig := wbb.BrokerDriver.Config + if brokerDriverConfig == nil { + brokerDriverConfig = wbb.BrokerDriver.Properties + } brokerDriver, err := r.registry.CreateBrokerDriver(brokerDriverType, brokerDriverConfig) if err != nil { return fmt.Errorf("failed to create broker-driver for WebBrokerApi %q: %w", wbb.Name, err) @@ -352,7 +365,12 @@ func (r *Runtime) LoadChannels(channelsPath string) error { Context: wbb.Context, Version: wbb.Version, Vhost: vhost, - Topics: topics, + Topics: allTopics, + Metadata: map[string]interface{}{ + "channelChains": channelChainsToMap(channelChains), + "topicToChannel": topicToChannel, + "channelNames": getChannelNames(wbb.Channels), + }, } // Create WebBrokerApi receiver. @@ -378,7 +396,8 @@ func (r *Runtime) LoadChannels(channelsPath string) error { "context", wbb.Context, "version", wbb.Version, "receiver", receiverType, - "topics", topics, + "topics", allTopics, + "channels", len(wbb.Channels), ) } @@ -719,37 +738,152 @@ func (r *Runtime) buildWebSubApiPolicyChains(wsb binding.WebSubApiBinding, vhost return subscribeKey, inboundKey, outboundKey, channelChainKeys, nil } -func (r *Runtime) buildWebBrokerApiPolicyChains(wbb binding.WebBrokerApiBinding, vhost string) (connInitReqKey, connInitRespKey, produceKey, consumeKey string, err error) { +// ChannelPolicyChains holds policy chain keys for a single channel. +type ChannelPolicyChains struct { + ConnInitReqKey string + ConnInitRespKey string + ProduceKey string + ConsumeKey string +} + +func (r *Runtime) buildWebBrokerApiPolicyChains(wbb binding.WebBrokerApiBinding, vhost string, channelName string) (connInitReqKey, connInitRespKey, produceKey, consumeKey string, err error) { basePath := wbb.Context if wbb.Version != "" { basePath = path.Join(wbb.Context, wbb.Version) } + suffix := "" + if channelName != "" { + suffix = "_" + channelName + } + // Connection init request chain: on_connection_init.request policies. - connInitReqKey = binding.GenerateRouteKey("CONNECT_INIT", basePath, vhost) + connInitReqKey = binding.GenerateRouteKey("CONNECT_INIT"+suffix, basePath, vhost) // Connection init response chain: on_connection_init.response policies (optional). - connInitRespKey = binding.GenerateRouteKey("CONNECT_INIT_RESP", basePath, vhost) + connInitRespKey = binding.GenerateRouteKey("CONNECT_INIT_RESP"+suffix, basePath, vhost) // Produce chain: on_produce policies (client → broker). - produceKey = binding.GenerateRouteKey("PRODUCE", basePath, vhost) + produceKey = binding.GenerateRouteKey("PRODUCE"+suffix, basePath, vhost) // Consume chain: on_consume policies (broker → client). - consumeKey = binding.GenerateRouteKey("CONSUME", basePath, vhost) + consumeKey = binding.GenerateRouteKey("CONSUME"+suffix, basePath, vhost) + + var onConnInitReq, onConnInitResp, onProduce, onConsume []binding.PolicyRef + + if channelName == "" { + // Build API-level policies + onConnInitReq = wbb.Policies.OnConnectionInit.Request + onConnInitResp = wbb.Policies.OnConnectionInit.Response + onProduce = wbb.Policies.OnProduce + onConsume = wbb.Policies.OnConsume + } else { + // Build channel-specific policies + if channelDef, ok := wbb.Channels[channelName]; ok { + onConnInitReq = channelDef.OnConnectionInit.Request + onConnInitResp = channelDef.OnConnectionInit.Response + onProduce = channelDef.OnProduce + onConsume = channelDef.OnConsume + } + } - if err = r.buildChain(connInitReqKey, wbb.Policies.OnConnectionInit.Request); err != nil { + if err = r.buildChain(connInitReqKey, onConnInitReq); err != nil { return "", "", "", "", err } - if err = r.buildChain(connInitRespKey, wbb.Policies.OnConnectionInit.Response); err != nil { + if err = r.buildChain(connInitRespKey, onConnInitResp); err != nil { return "", "", "", "", err } - if err = r.buildChain(produceKey, wbb.Policies.OnProduce); err != nil { + if err = r.buildChain(produceKey, onProduce); err != nil { return "", "", "", "", err } - if err = r.buildChain(consumeKey, wbb.Policies.OnConsume); err != nil { + if err = r.buildChain(consumeKey, onConsume); err != nil { return "", "", "", "", err } return connInitReqKey, connInitRespKey, produceKey, consumeKey, nil } +// extractTopicsFromChannelPolicies extracts Kafka topics to subscribe to from a channel's on_consume policies. +// Only extracts topics from map-topic policies with mode="consumeFrom". +// These topics are used for Kafka consumer subscription. +func extractTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) []string { + topics := make(map[string]bool) // Use map to deduplicate + + // ONLY check onConsume policies - these are the topics we subscribe to + for _, policy := range channelDef.OnConsume { + if policy.Name == "map-topic" && policy.Params != nil { + if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { + if topic, ok := policy.Params["topic"].(string); ok && topic != "" { + topics[topic] = true + } + } + } + } + + // Convert map to slice + result := make([]string, 0, len(topics)) + for topic := range topics { + result = append(result, topic) + } + return result +} + +// extractAllTopicsFromChannelPolicies extracts ALL Kafka topics (both produce and consume) from a channel. +// Used to ensure all necessary topics exist in Kafka before the API starts. +func extractAllTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) []string { + topics := make(map[string]bool) // Use map to deduplicate + + // Check onProduce policies for produceTo topics + for _, policy := range channelDef.OnProduce { + if policy.Name == "map-topic" && policy.Params != nil { + if mode, ok := policy.Params["mode"].(string); ok && mode == "produceTo" { + if topic, ok := policy.Params["topic"].(string); ok && topic != "" { + topics[topic] = true + } + } + } + } + + // Check onConsume policies for consumeFrom topics + for _, policy := range channelDef.OnConsume { + if policy.Name == "map-topic" && policy.Params != nil { + if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { + if topic, ok := policy.Params["topic"].(string); ok && topic != "" { + topics[topic] = true + } + } + } + } + + // Convert map to slice + result := make([]string, 0, len(topics)) + for topic := range topics { + result = append(result, topic) + } + return result +} + +// getChannelNames extracts channel names from the channels map. +func getChannelNames(channels map[string]binding.WebBrokerChannelDef) []string { + names := make([]string, 0, len(channels)) + for name := range channels { + names = append(names, name) + } + return names +} + +// channelChainsToMap converts ChannelPolicyChains map to a map structure +// that can be easily accessed from other packages without type dependencies. +func channelChainsToMap(chains map[string]ChannelPolicyChains) map[string]map[string]string { + result := make(map[string]map[string]string, len(chains)) + for channelName, chainKeys := range chains { + result[channelName] = map[string]string{ + "ConnInitReqKey": chainKeys.ConnInitReqKey, + "ConnInitRespKey": chainKeys.ConnInitRespKey, + "ProduceKey": chainKeys.ProduceKey, + "ConsumeKey": chainKeys.ConsumeKey, + } + } + return result +} + func (r *Runtime) buildChain(routeKey string, policies []binding.PolicyRef) error { if routeKey == "" { return nil @@ -972,21 +1106,47 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error vhost := defaultVhost(wbb.Vhost) - // Build policy chains for the API. - connInitReqKey, _, produceKey, consumeKey, err := r.buildWebBrokerApiPolicyChains(wbb, vhost) + // Build API-level policy chains. + apiConnInitReqKey, _, _, _, err := r.buildWebBrokerApiPolicyChains(wbb, vhost, "") if err != nil { r.mu.Unlock() - return fmt.Errorf("failed to build chains for WebBrokerApi %q: %w", wbb.Name, err) + return fmt.Errorf("failed to build API-level chains for WebBrokerApi %q: %w", wbb.Name, err) } - // Extract broker-driver properties. - brokerProperties := wbb.BrokerDriver.Config - slog.Info("DEBUG: BrokerDriver config received", "config", brokerProperties, "channel", wbb.Name) - topics := []string{} - if topic, ok := brokerProperties["topic"].(string); ok && topic != "" { - topics = append(topics, topic) + // Build per-channel policy chains and collect topics. + channelChains := make(map[string]ChannelPolicyChains) + allTopics := []string{} // All topics (produce + consume) for ensuring they exist + topicToChannel := make(map[string]string) // Only consume topics for subscription mapping + + for channelName, channelDef := range wbb.Channels { + connInitReqKey, connInitRespKey, produceKey, consumeKey, err := r.buildWebBrokerApiPolicyChains(wbb, vhost, channelName) + if err != nil { + r.mu.Unlock() + return fmt.Errorf("failed to build chains for channel %q in WebBrokerApi %q: %w", channelName, wbb.Name, err) + } + + channelChains[channelName] = ChannelPolicyChains{ + ConnInitReqKey: connInitReqKey, + ConnInitRespKey: connInitRespKey, + ProduceKey: produceKey, + ConsumeKey: consumeKey, + } + + // Extract ALL topics (produce + consume) to ensure they exist in Kafka + allChannelTopics := extractAllTopicsFromChannelPolicies(channelDef) + allTopics = append(allTopics, allChannelTopics...) + + // Extract ONLY consume topics for subscription mapping + consumeTopics := extractTopicsFromChannelPolicies(channelDef) + for _, topic := range consumeTopics { + topicToChannel[topic] = channelName + } + + slog.Info("Built policy chains for WebBrokerApi channel", + "api", wbb.Name, + "channel", channelName, + "topics", allChannelTopics) } - slog.Info("DEBUG: Topics extracted", "topics", topics, "channel", wbb.Name) r.hub.RegisterBinding(hub.ChannelBinding{ APIID: wbb.APIID, @@ -995,9 +1155,9 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error Context: wbb.Context, Version: wbb.Version, Vhost: vhost, - SubscribeChainKey: connInitReqKey, - InboundChainKey: produceKey, - OutboundChainKey: consumeKey, + SubscribeChainKey: apiConnInitReqKey, + InboundChainKey: "", // Determined per-channel + OutboundChainKey: "", // Determined per-channel }) // Create broker-driver. @@ -1018,7 +1178,12 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error Context: wbb.Context, Version: wbb.Version, Vhost: vhost, - Topics: topics, + Topics: allTopics, + Metadata: map[string]interface{}{ + "channelChains": channelChainsToMap(channelChains), + "topicToChannel": topicToChannel, + "channelNames": getChannelNames(wbb.Channels), + }, } // Determine receiver type (websocket, sse, etc.) @@ -1058,7 +1223,9 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error "name", wbb.Name, "context", wbb.Context, "version", wbb.Version, - "receiver_type", receiverType) + "receiver_type", receiverType, + "channels", len(wbb.Channels), + "topics", allTopics) return nil } diff --git a/event-gateway/gateway-runtime/internal/xdsclient/handler.go b/event-gateway/gateway-runtime/internal/xdsclient/handler.go index 2599c62cf..4cc8e7344 100644 --- a/event-gateway/gateway-runtime/internal/xdsclient/handler.go +++ b/event-gateway/gateway-runtime/internal/xdsclient/handler.go @@ -49,17 +49,23 @@ type KafkaConfig struct { // EventChannelResource represents the decoded EventChannelConfig JSON payload. type EventChannelResource struct { - UUID string `json:"uuid"` - Name string `json:"name"` - Kind string `json:"kind"` - Context string `json:"context"` - Version string `json:"version"` - Deleted bool `json:"deleted,omitempty"` - Channels []ChannelEntry `json:"channels"` - Receiver ReceiverEntry `json:"receiver"` - BrokerDriver BrokerDriverEntry `json:"broker-driver"` - Policies PoliciesEntry `json:"policies"` - AllChannelPolicies *ProtocolMediationPolicies `json:"allChannelPolicies,omitempty"` + UUID string `json:"uuid"` + Name string `json:"name"` + Kind string `json:"kind"` + Context string `json:"context"` + Version string `json:"version"` + Deleted bool `json:"deleted,omitempty"` + Channels interface{} `json:"channels"` // []ChannelEntry for WebSubApi, map[string]WebBrokerChannelEntry for WebBrokerApi + Receiver ReceiverEntry `json:"receiver"` // For WebBrokerApi + BrokerDriver BrokerDriverEntry `json:"broker-driver"` // For WebBrokerApi + Policies interface{} `json:"policies"` // PoliciesEntry for WebSubApi, ProtocolMediationPolicies for WebBrokerApi +} + +// WebBrokerChannelEntry represents a WebBrokerApi channel with policies +type WebBrokerChannelEntry struct { + OnConnectionInit ConnectionInitPolicies `json:"on_connection_init"` + OnProduce []PolicyEntry `json:"on_produce"` + OnConsume []PolicyEntry `json:"on_consume"` } // ProtocolMediationPolicies defines policies for WebBrokerApi @@ -83,11 +89,13 @@ type ChannelEntry struct { // ReceiverEntry specifies the receiver type. type ReceiverEntry struct { + Name string `json:"name"` Type string `json:"type"` } // BrokerDriverEntry specifies the broker driver configuration. type BrokerDriverEntry struct { + Name string `json:"name"` Type string `json:"type"` Properties map[string]interface{} `json:"properties"` } @@ -216,8 +224,36 @@ func (h *Handler) HandleResources(ctx context.Context, resources []*discoveryv3. } func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApiBinding { - channels := make([]binding.ChannelDef, len(ecr.Channels)) - for i, ch := range ecr.Channels { + // For WebSubApi, Channels is []ChannelEntry + var channelEntries []ChannelEntry + if ecr.Channels != nil { + // Try to decode as []ChannelEntry (for WebSubApi) + if channelsSlice, ok := ecr.Channels.([]interface{}); ok { + for _, chIface := range channelsSlice { + if chMap, ok := chIface.(map[string]interface{}); ok { + var ch ChannelEntry + if name, ok := chMap["name"].(string); ok { + ch.Name = name + } + if policiesIface, ok := chMap["policies"].(map[string]interface{}); ok { + if subIface, ok := policiesIface["subscribe"].([]interface{}); ok { + ch.Policies.Subscribe = mapGenericPolicyEntryList(subIface) + } + if inIface, ok := policiesIface["inbound"].([]interface{}); ok { + ch.Policies.Inbound = mapGenericPolicyEntryList(inIface) + } + if outIface, ok := policiesIface["outbound"].([]interface{}); ok { + ch.Policies.Outbound = mapGenericPolicyEntryList(outIface) + } + } + channelEntries = append(channelEntries, ch) + } + } + } + } + + channels := make([]binding.ChannelDef, len(channelEntries)) + for i, ch := range channelEntries { channels[i] = binding.ChannelDef{ Name: ch.Name, Policies: binding.PolicyBindings{ @@ -228,9 +264,25 @@ func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApi } } - subscribe := mapPolicyEntries(ecr.Policies.Subscribe) - inbound := mapPolicyEntries(ecr.Policies.Inbound) - outbound := mapPolicyEntries(ecr.Policies.Outbound) + // For WebSubApi, Policies is PoliciesEntry + var policies PoliciesEntry + if ecr.Policies != nil { + if policiesMap, ok := ecr.Policies.(map[string]interface{}); ok { + if subIface, ok := policiesMap["subscribe"].([]interface{}); ok { + policies.Subscribe = mapGenericPolicyEntryList(subIface) + } + if inIface, ok := policiesMap["inbound"].([]interface{}); ok { + policies.Inbound = mapGenericPolicyEntryList(inIface) + } + if outIface, ok := policiesMap["outbound"].([]interface{}); ok { + policies.Outbound = mapGenericPolicyEntryList(outIface) + } + } + } + + subscribe := mapPolicyEntries(policies.Subscribe) + inbound := mapPolicyEntries(policies.Inbound) + outbound := mapPolicyEntries(policies.Outbound) return binding.WebSubApiBinding{ Kind: "WebSubApi", @@ -251,6 +303,27 @@ func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApi } } +// mapGenericPolicyEntryList converts []interface{} to []PolicyEntry +func mapGenericPolicyEntryList(policies []interface{}) []PolicyEntry { + result := make([]PolicyEntry, 0, len(policies)) + for _, pIface := range policies { + if pMap, ok := pIface.(map[string]interface{}); ok { + policyEntry := PolicyEntry{} + if name, ok := pMap["name"].(string); ok { + policyEntry.Name = name + } + if version, ok := pMap["version"].(string); ok { + policyEntry.Version = version + } + if params, ok := pMap["params"].(map[string]interface{}); ok { + policyEntry.Params = params + } + result = append(result, policyEntry) + } + } + return result +} + // mapPolicyEntries converts a slice of PolicyEntry to binding.PolicyRef. func mapPolicyEntries(entries []PolicyEntry) []binding.PolicyRef { if len(entries) == 0 { @@ -280,6 +353,7 @@ func (h *Handler) resolveBrokerDriver(bd BrokerDriverEntry) binding.BrokerDriver } return binding.BrokerDriverSpec{ + Name: bd.Name, Type: driverType, Config: cfg, } @@ -313,13 +387,60 @@ func (h *Handler) removeBinding(ecr EventChannelResource) error { // toWebBrokerApiBinding converts EventChannelResource to WebBrokerApiBinding. func (h *Handler) toWebBrokerApiBinding(ecr EventChannelResource) binding.WebBrokerApiBinding { - var connInitReq, connInitResp, onProduce, onConsume []binding.PolicyRef + // Parse API-level policies from Policies field (interface{}) + var apiPolicies binding.ProtocolMediationPolicies + if ecr.Policies != nil { + if policiesMap, ok := ecr.Policies.(map[string]interface{}); ok { + // Parse on_connection_init + if connInitIface, ok := policiesMap["on_connection_init"].(map[string]interface{}); ok { + if reqIface, ok := connInitIface["request"].([]interface{}); ok { + apiPolicies.OnConnectionInit.Request = mapGenericPolicyList(reqIface) + } + if respIface, ok := connInitIface["response"].([]interface{}); ok { + apiPolicies.OnConnectionInit.Response = mapGenericPolicyList(respIface) + } + } + // Parse on_produce + if produceIface, ok := policiesMap["on_produce"].([]interface{}); ok { + apiPolicies.OnProduce = mapGenericPolicyList(produceIface) + } + // Parse on_consume + if consumeIface, ok := policiesMap["on_consume"].([]interface{}); ok { + apiPolicies.OnConsume = mapGenericPolicyList(consumeIface) + } + } + } - if ecr.AllChannelPolicies != nil { - connInitReq = mapPolicyEntries(ecr.AllChannelPolicies.OnConnectionInit.Request) - connInitResp = mapPolicyEntries(ecr.AllChannelPolicies.OnConnectionInit.Response) - onProduce = mapPolicyEntries(ecr.AllChannelPolicies.OnProduce) - onConsume = mapPolicyEntries(ecr.AllChannelPolicies.OnConsume) + // Parse channels map from Channels field (interface{}) + channels := make(map[string]binding.WebBrokerChannelDef) + if ecr.Channels != nil { + if channelsMap, ok := ecr.Channels.(map[string]interface{}); ok { + for channelName, channelIface := range channelsMap { + if channelData, ok := channelIface.(map[string]interface{}); ok { + var channelDef binding.WebBrokerChannelDef + + // Parse channel on_connection_init + if connInitIface, ok := channelData["on_connection_init"].(map[string]interface{}); ok { + if reqIface, ok := connInitIface["request"].([]interface{}); ok { + channelDef.OnConnectionInit.Request = mapGenericPolicyList(reqIface) + } + if respIface, ok := connInitIface["response"].([]interface{}); ok { + channelDef.OnConnectionInit.Response = mapGenericPolicyList(respIface) + } + } + // Parse channel on_produce + if produceIface, ok := channelData["on_produce"].([]interface{}); ok { + channelDef.OnProduce = mapGenericPolicyList(produceIface) + } + // Parse channel on_consume + if consumeIface, ok := channelData["on_consume"].([]interface{}); ok { + channelDef.OnConsume = mapGenericPolicyList(consumeIface) + } + + channels[channelName] = channelDef + } + } + } } return binding.WebBrokerApiBinding{ @@ -329,16 +450,32 @@ func (h *Handler) toWebBrokerApiBinding(ecr EventChannelResource) binding.WebBro Version: ecr.Version, Context: ecr.Context, Receiver: binding.ReceiverSpec{ + Name: ecr.Receiver.Name, Type: ecr.Receiver.Type, }, BrokerDriver: h.resolveBrokerDriver(ecr.BrokerDriver), - Policies: binding.ProtocolMediationPolicies{ - OnConnectionInit: binding.ConnectionInitPolicies{ - Request: connInitReq, - Response: connInitResp, - }, - OnProduce: onProduce, - OnConsume: onConsume, - }, + Policies: apiPolicies, + Channels: channels, + } +} + +// mapGenericPolicyList converts []interface{} to []binding.PolicyRef +func mapGenericPolicyList(policies []interface{}) []binding.PolicyRef { + result := make([]binding.PolicyRef, 0, len(policies)) + for _, pIface := range policies { + if pMap, ok := pIface.(map[string]interface{}); ok { + policyRef := binding.PolicyRef{} + if name, ok := pMap["name"].(string); ok { + policyRef.Name = name + } + if version, ok := pMap["version"].(string); ok { + policyRef.Version = version + } + if params, ok := pMap["params"].(map[string]interface{}); ok { + policyRef.Params = params + } + result = append(result, policyRef) + } } + return result } diff --git a/event-gateway/gateway-runtime/internal/xdsclient/handler_test.go b/event-gateway/gateway-runtime/internal/xdsclient/handler_test.go index c497b174c..a9bcaf4bf 100644 --- a/event-gateway/gateway-runtime/internal/xdsclient/handler_test.go +++ b/event-gateway/gateway-runtime/internal/xdsclient/handler_test.go @@ -167,6 +167,16 @@ func (m *recordingBindingManager) RemoveWebSubApiBinding(name string) error { return nil } +func (m *recordingBindingManager) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error { + m.added = append(m.added, wbb.Name) + return nil +} + +func (m *recordingBindingManager) RemoveWebBrokerApiBinding(name string) error { + m.removed = append(m.removed, name) + return nil +} + func (m *recordingBindingManager) addedNames() []string { out := append([]string(nil), m.added...) return out diff --git a/event-gateway/policies/map-topic/go.mod b/event-gateway/policies/map-topic/go.mod new file mode 100644 index 000000000..0aef3fdb5 --- /dev/null +++ b/event-gateway/policies/map-topic/go.mod @@ -0,0 +1,5 @@ +module github.com/wso2/api-platform/event-gateway/policies/map-topic + +go 1.26.2 + +require github.com/wso2/api-platform/sdk/core v0.2.9 diff --git a/event-gateway/policies/map-topic/maptopic.go b/event-gateway/policies/map-topic/maptopic.go new file mode 100644 index 000000000..e96457fbf --- /dev/null +++ b/event-gateway/policies/map-topic/maptopic.go @@ -0,0 +1,70 @@ +package maptopic + +import ( + "context" + "log/slog" + + policy "github.com/wso2/api-platform/sdk/core/policy/v1alpha2" +) + +// MapTopicPolicy routes messages to specific Kafka topics in WebBrokerApi channels. +// For mode="produceTo": Sets the target topic on outbound messages +// For mode="consumeFrom": Provides metadata for Kafka consumer subscription (no processing) +type MapTopicPolicy struct { + mode string + topic string +} + +// GetPolicy returns the policy instance. +func GetPolicy( + metadata policy.PolicyMetadata, + params map[string]interface{}, +) (policy.Policy, error) { + mode := "" + topic := "" + + if modeVal, ok := params["mode"]; ok { + if modeStr, ok := modeVal.(string); ok { + mode = modeStr + } + } + + if topicVal, ok := params["topic"]; ok { + if topicStr, ok := topicVal.(string); ok { + topic = topicStr + } + } + + slog.Debug("[Map Topic]: Policy created", "mode", mode, "topic", topic) + return &MapTopicPolicy{mode: mode, topic: topic}, nil +} + +// Mode returns the processing mode for this policy. +func (p *MapTopicPolicy) Mode() policy.ProcessingMode { + if p.mode == "produceTo" { + // For produceTo mode, we need to process the request body to set the topic + return policy.ProcessingMode{ + RequestHeaderMode: policy.HeaderModeSkip, + RequestBodyMode: policy.BodyModeBuffer, // Need to set topic on message + ResponseHeaderMode: policy.HeaderModeSkip, + ResponseBodyMode: policy.BodyModeSkip, + } + } + // For consumeFrom mode, this is metadata-only, skip all processing + return policy.ProcessingMode{ + RequestHeaderMode: policy.HeaderModeSkip, + RequestBodyMode: policy.BodyModeSkip, + ResponseHeaderMode: policy.HeaderModeSkip, + ResponseBodyMode: policy.BodyModeSkip, + } +} + +// OnRequestBody sets the Kafka topic for produceTo mode. +func (p *MapTopicPolicy) OnRequestBody(_ context.Context, ctx *policy.RequestContext, params map[string]interface{}) policy.RequestAction { + if p.mode == "produceTo" && p.topic != "" { + // Store the topic in metadata so the hub can extract it and apply to the message + ctx.Metadata["kafka.topic"] = p.topic + slog.Debug("[Map Topic]: Set target topic in metadata", "topic", p.topic) + } + return nil +} diff --git a/event-gateway/policies/map-topic/policy-definition.yaml b/event-gateway/policies/map-topic/policy-definition.yaml new file mode 100644 index 000000000..92f5c0797 --- /dev/null +++ b/event-gateway/policies/map-topic/policy-definition.yaml @@ -0,0 +1,35 @@ +name: map-topic +version: v1.0.0 +displayName: Map Topic +description: | + Configures the Kafka topic for producing to or consuming from in WebBrokerApi channels. + This policy specifies the exact topic name at the broker level for channel-based routing. + +parameters: + type: object + additionalProperties: false + properties: + mode: + type: string + description: | + Specifies the operation mode: "produceTo" for producing messages to a topic, + or "consumeFrom" for consuming messages from a topic. + enum: + - produceTo + - consumeFrom + + topic: + type: string + description: | + The Kafka topic name to produce to or consume from. + minLength: 1 + maxLength: 256 + pattern: "^[a-zA-Z0-9._-]+$" + + required: + - mode + - topic + +systemParameters: + type: object + properties: {} diff --git a/gateway/gateway-builder/go.mod b/gateway/gateway-builder/go.mod index 1144a9e24..322da0cd3 100644 --- a/gateway/gateway-builder/go.mod +++ b/gateway/gateway-builder/go.mod @@ -5,12 +5,14 @@ go 1.26.2 require ( github.com/stretchr/testify v1.11.1 github.com/wso2/api-platform/sdk/core v0.2.9 - golang.org/x/mod v0.32.0 + golang.org/x/mod v0.33.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/gateway/gateway-builder/go.sum b/gateway/gateway-builder/go.sum index 8dd8a86ba..41ffd4c29 100644 --- a/gateway/gateway-builder/go.sum +++ b/gateway/gateway-builder/go.sum @@ -1,18 +1,18 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/wso2/api-platform/sdk/core v0.2.9 h1:3lvAsMlLhy8nNgPL24/UFS/f4sq5e+XpryA4L1PO7dU= github.com/wso2/api-platform/sdk/core v0.2.9/go.mod h1:vgNVzR16g9k5cun3VXZ7wDg8UGbPxsVU2TW8EbRCv0o= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/gateway/gateway-controller/go.mod b/gateway/gateway-controller/go.mod index b4546a837..e46980d28 100644 --- a/gateway/gateway-controller/go.mod +++ b/gateway/gateway-controller/go.mod @@ -9,7 +9,7 @@ require ( github.com/gin-gonic/gin v1.11.0 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/jackc/pgx/v5 v5.9.2 github.com/jmoiron/sqlx v1.4.0 github.com/knadh/koanf/parsers/toml/v2 v2.2.0 @@ -88,6 +88,8 @@ require ( github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/arch v0.23.0 // indirect golang.org/x/crypto v0.48.0 // indirect @@ -96,8 +98,8 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect ) replace github.com/wso2/api-platform/common => ../../common diff --git a/gateway/gateway-controller/go.sum b/gateway/gateway-controller/go.sum index 67374bd12..26abe94f4 100644 --- a/gateway/gateway-controller/go.sum +++ b/gateway/gateway-controller/go.sum @@ -81,8 +81,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -200,16 +200,16 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= +go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= +go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= +go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -233,10 +233,10 @@ golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/gateway/gateway-controller/pkg/api/management/webbroker_types.go b/gateway/gateway-controller/pkg/api/management/webbroker_types.go index 5b6bb8ac6..5c2c2087b 100644 --- a/gateway/gateway-controller/pkg/api/management/webbroker_types.go +++ b/gateway/gateway-controller/pkg/api/management/webbroker_types.go @@ -72,8 +72,11 @@ type WebBrokerApiData struct { // BrokerDriver Broker driver configuration - message broker adapter (Kafka, MQTT, AMQP) BrokerDriver WebBrokerApiBrokerDriver `json:"brokerDriver" yaml:"brokerDriver"` - // AllChannelPolicies Protocol mediation policies applied to all channels - AllChannelPolicies *WebBrokerApiPolicies `json:"allChannelPolicies,omitempty" yaml:"allChannelPolicies,omitempty"` + // Policies API-level policies applied to all channels + Policies *WebBrokerApiPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` + + // Channels Channel-specific configurations with policies per channel + Channels map[string]WebBrokerApiChannel `json:"channels,omitempty" yaml:"channels,omitempty"` // Version Semantic version of the API Version string `json:"version" yaml:"version"` @@ -93,6 +96,9 @@ type WebBrokerApiDataDeploymentState string // WebBrokerApiReceiver Receiver configuration for protocol adapter type WebBrokerApiReceiver struct { + // Name Receiver instance name + Name string `json:"name" yaml:"name"` + // Type Receiver type (websocket, sse) Type string `json:"type" yaml:"type"` @@ -102,10 +108,13 @@ type WebBrokerApiReceiver struct { // WebBrokerApiBrokerDriver Broker driver configuration type WebBrokerApiBrokerDriver struct { + // Name Broker driver instance name + Name string `json:"name" yaml:"name"` + // Type Broker driver type (kafka, mqtt, amqp) Type string `json:"type" yaml:"type"` - // Properties Broker-specific configuration properties (topic, bootstrap.servers, etc.) + // Properties Broker-specific configuration properties (bootstrap.servers, etc.) Properties map[string]interface{} `json:"properties" yaml:"properties"` } @@ -129,3 +138,15 @@ type WebBrokerApiConnectionInitPolicies struct { // Response Policies applied after WebSocket upgrade Response *[]Policy `json:"response,omitempty" yaml:"response,omitempty"` } + +// WebBrokerApiChannel Channel-specific configuration with policies +type WebBrokerApiChannel struct { + // OnConnectionInit Policies applied during WebSocket handshake for this channel + OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` + + // OnProduce Policies applied when client sends message to broker on this channel + OnProduce *[]Policy `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` + + // OnConsume Policies applied when broker message delivered to client on this channel + OnConsume *[]Policy `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` +} diff --git a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go index b66bda525..b89604ddf 100644 --- a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go +++ b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go @@ -159,6 +159,7 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke // Build receiver configuration receiver := map[string]interface{}{ + "name": spec.Receiver.Name, "type": spec.Receiver.Type, } if spec.Receiver.Properties != nil { @@ -167,34 +168,71 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke // Build broker-driver configuration brokerDriver := map[string]interface{}{ + "name": spec.BrokerDriver.Name, "type": spec.BrokerDriver.Type, "properties": spec.BrokerDriver.Properties, } slog.Info("DEBUG: Building EventChannelConfig for WebBrokerApi", "name", webBrokerCfg.Metadata.Name, - "brokerDriverType", spec.BrokerDriver.Type, - "brokerDriverConfig", spec.BrokerDriver.Properties) - - // Build protocol mediation policies - var onConnectionInitRequest []interface{} - var onConnectionInitResponse []interface{} - var onProduce []interface{} - var onConsume []interface{} - - if spec.AllChannelPolicies != nil { - if spec.AllChannelPolicies.OnConnectionInit != nil { - if spec.AllChannelPolicies.OnConnectionInit.Request != nil { - onConnectionInitRequest = buildPolicyList(spec.AllChannelPolicies.OnConnectionInit.Request) + "receiverName", spec.Receiver.Name, + "brokerDriverName", spec.BrokerDriver.Name, + "brokerDriverType", spec.BrokerDriver.Type) + + // Build API-level policies + var apiOnConnectionInitRequest []interface{} + var apiOnConnectionInitResponse []interface{} + var apiOnProduce []interface{} + var apiOnConsume []interface{} + + if spec.Policies != nil { + if spec.Policies.OnConnectionInit != nil { + if spec.Policies.OnConnectionInit.Request != nil { + apiOnConnectionInitRequest = buildPolicyList(spec.Policies.OnConnectionInit.Request) } - if spec.AllChannelPolicies.OnConnectionInit.Response != nil { - onConnectionInitResponse = buildPolicyList(spec.AllChannelPolicies.OnConnectionInit.Response) + if spec.Policies.OnConnectionInit.Response != nil { + apiOnConnectionInitResponse = buildPolicyList(spec.Policies.OnConnectionInit.Response) } } - if spec.AllChannelPolicies.OnProduce != nil { - onProduce = buildPolicyList(spec.AllChannelPolicies.OnProduce) + if spec.Policies.OnProduce != nil { + apiOnProduce = buildPolicyList(spec.Policies.OnProduce) } - if spec.AllChannelPolicies.OnConsume != nil { - onConsume = buildPolicyList(spec.AllChannelPolicies.OnConsume) + if spec.Policies.OnConsume != nil { + apiOnConsume = buildPolicyList(spec.Policies.OnConsume) + } + } + + // Build channels map with channel-specific policies + channels := make(map[string]interface{}) + if spec.Channels != nil { + for channelName, channelConfig := range spec.Channels { + var channelOnConnectionInitRequest []interface{} + var channelOnConnectionInitResponse []interface{} + var channelOnProduce []interface{} + var channelOnConsume []interface{} + + if channelConfig.OnConnectionInit != nil { + if channelConfig.OnConnectionInit.Request != nil { + channelOnConnectionInitRequest = buildPolicyList(channelConfig.OnConnectionInit.Request) + } + if channelConfig.OnConnectionInit.Response != nil { + channelOnConnectionInitResponse = buildPolicyList(channelConfig.OnConnectionInit.Response) + } + } + if channelConfig.OnProduce != nil { + channelOnProduce = buildPolicyList(channelConfig.OnProduce) + } + if channelConfig.OnConsume != nil { + channelOnConsume = buildPolicyList(channelConfig.OnConsume) + } + + channels[channelName] = map[string]interface{}{ + "on_connection_init": map[string]interface{}{ + "request": channelOnConnectionInitRequest, + "response": channelOnConnectionInitResponse, + }, + "on_produce": channelOnProduce, + "on_consume": channelOnConsume, + } } } @@ -206,14 +244,15 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke "version": spec.Version, "receiver": receiver, "broker-driver": brokerDriver, - "allChannelPolicies": map[string]interface{}{ + "policies": map[string]interface{}{ "on_connection_init": map[string]interface{}{ - "request": onConnectionInitRequest, - "response": onConnectionInitResponse, + "request": apiOnConnectionInitRequest, + "response": apiOnConnectionInitResponse, }, - "on_produce": onProduce, - "on_consume": onConsume, + "on_produce": apiOnProduce, + "on_consume": apiOnConsume, }, + "channels": channels, } return toAnyResource(data, EventChannelConfigTypeURL) diff --git a/gateway/gateway-runtime/policy-engine/internal/executor/chain.go b/gateway/gateway-runtime/policy-engine/internal/executor/chain.go index 4649ce8c7..29f3c8a3d 100644 --- a/gateway/gateway-runtime/policy-engine/internal/executor/chain.go +++ b/gateway/gateway-runtime/policy-engine/internal/executor/chain.go @@ -22,11 +22,12 @@ import ( "context" "encoding/json" "fmt" - "github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/utils" "log/slog" "strings" "time" + "github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/utils" + "github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/constants" "github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/metrics" "github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/registry" @@ -213,8 +214,9 @@ type RequestPolicyResult struct { // RequestExecutionResult represents the result of executing all request policies in a chain type RequestExecutionResult struct { Results []RequestPolicyResult - ShortCircuited bool // true if chain stopped early due to ImmediateResponse - FinalAction policy.RequestAction // Final action to apply + ShortCircuited bool // true if chain stopped early due to ImmediateResponse + FinalAction policy.RequestAction // Final action to apply + Metadata map[string]interface{} // Metadata from SharedContext (for inter-policy communication) TotalExecutionTime time.Duration } @@ -361,6 +363,10 @@ func (c *ChainExecutor) ExecuteRequestPolicies(ctx context.Context, policyList [ } result.TotalExecutionTime = time.Since(startTime) + // Capture metadata from SharedContext for inter-policy communication + if reqCtx != nil && reqCtx.SharedContext != nil { + result.Metadata = reqCtx.SharedContext.Metadata + } return result, nil } diff --git a/gateway/gateway-runtime/policy-engine/pkg/engine/types.go b/gateway/gateway-runtime/policy-engine/pkg/engine/types.go index dd56efc17..f58f5b5dd 100644 --- a/gateway/gateway-runtime/policy-engine/pkg/engine/types.go +++ b/gateway/gateway-runtime/policy-engine/pkg/engine/types.go @@ -41,6 +41,7 @@ type RequestBodyResult struct { HeadersToSet map[string]string HeadersToRemove []string Body []byte + Topic string // Kafka topic for publish (set by policies like map-topic) ShortCircuited bool ImmediateResponse *ImmediateResponseResult TotalDuration time.Duration @@ -134,6 +135,13 @@ func mapRequestBodyResult(r *executor.RequestExecutionResult) *RequestBodyResult TotalDuration: r.TotalExecutionTime, } + // Extract topic from metadata if set by policies (e.g., map-topic policy) + if r.Metadata != nil { + if topic, ok := r.Metadata["kafka.topic"].(string); ok && topic != "" { + res.Topic = topic + } + } + if r.FinalAction != nil { switch a := r.FinalAction.(type) { case policy.UpstreamRequestModifications: diff --git a/gateway/it/go.mod b/gateway/it/go.mod index bafae2a33..6ebbaed7e 100644 --- a/gateway/it/go.mod +++ b/gateway/it/go.mod @@ -67,7 +67,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsevents v0.2.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect @@ -77,21 +77,21 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.3 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -138,6 +138,8 @@ require ( github.com/oapi-codegen/runtime v1.1.2 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/onsi/ginkgo/v2 v2.22.0 // indirect + github.com/onsi/gomega v1.36.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect @@ -152,7 +154,7 @@ require ( github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect @@ -210,14 +212,15 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.32.3 // indirect - k8s.io/apimachinery v0.32.3 // indirect - k8s.io/client-go v0.32.3 // indirect + k8s.io/api v0.33.3 // indirect + k8s.io/apimachinery v0.33.3 // indirect + k8s.io/client-go v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect tags.cncf.io/container-device-interface v1.0.1 // indirect ) diff --git a/gateway/it/go.sum b/gateway/it/go.sum index da79dfa90..86b07c5e6 100644 --- a/gateway/it/go.sum +++ b/gateway/it/go.sum @@ -1,5 +1,6 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= @@ -165,8 +166,7 @@ github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0o github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -191,16 +191,13 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -230,16 +227,13 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -249,8 +243,7 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -390,14 +383,11 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtE github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -457,8 +447,7 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA= github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU= @@ -706,23 +695,19 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0= diff --git a/go.work b/go.work index 79043b29c..785ca50fd 100644 --- a/go.work +++ b/go.work @@ -5,8 +5,9 @@ use ( ./cli/src ./common ./event-gateway/gateway-builder - ./event-gateway/webhook-listener ./event-gateway/gateway-runtime + ./event-gateway/policies/map-topic + ./event-gateway/webhook-listener ./gateway/gateway-builder ./gateway/gateway-controller ./gateway/gateway-runtime/policy-engine diff --git a/go.work.sum b/go.work.sum index 2313c01f3..fc7b92a32 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2523,7 +2523,10 @@ github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= @@ -2678,6 +2681,7 @@ github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0/go.mod h1:KgnwoLYCZ8I github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= @@ -2813,7 +2817,6 @@ github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgj github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= @@ -3120,11 +3123,13 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= @@ -3240,6 +3245,7 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -3290,6 +3296,7 @@ github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSW github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= @@ -3497,6 +3504,7 @@ go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRl go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/jaeger v1.13.0 h1:VAMoGujbVV8Q0JNM/cEbhzUIWWBxnEqH45HP9iBKN04= go.opentelemetry.io/otel/exporters/jaeger v1.13.0/go.mod h1:fHwbmle6mBFJA1p2ZIhilvffCdq/dM5UTIiCOmEjS+w= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= @@ -3533,6 +3541,7 @@ go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9 go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= @@ -3550,6 +3559,7 @@ go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJ go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= @@ -3558,6 +3568,7 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRY go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= @@ -3576,6 +3587,7 @@ go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37Cb go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -3602,6 +3614,7 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -3752,6 +3765,7 @@ golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3840,6 +3854,7 @@ golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -3889,6 +3904,7 @@ golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -4020,6 +4036,7 @@ golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= @@ -4100,6 +4117,7 @@ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -4606,6 +4624,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go. google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:+34luvCflYKiKylNwGJfn9cFBbcL/WrkciMmDmsTQ/A= @@ -4753,6 +4772,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -4866,6 +4886,7 @@ google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/avro.v0 v0.0.0-20171217001914-a730b5802183 h1:PGIdqvwfpMUyUP+QAlAnKTSWQ671SmYjoou2/5j7HXk= @@ -4936,6 +4957,7 @@ k8s.io/kubelet v0.32.3/go.mod h1:yyAQSCKC+tjSlaFw4HQG7Jein+vo+GeKBGdXdQGvL1U= k8s.io/metrics v0.33.3 h1:9CcqBz15JZfISqwca33gdHS8I6XfsK1vA8WUdEnG70g= k8s.io/metrics v0.33.3/go.mod h1:Aw+cdg4AYHw0HvUY+lCyq40FOO84awrqvJRTw0cmXDs= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= kernel.org/pub/linux/libs/security/libcap/cap v1.2.76 h1:mrdLPj8ujM6eIKGtd1PkkuCIodpFFDM42Cfm0YODkIM= kernel.org/pub/linux/libs/security/libcap/cap v1.2.76/go.mod h1:7V2BQeHnVAQwhCnCPJ977giCeGDiywVewWF+8vkpPlc= kernel.org/pub/linux/libs/security/libcap/psx v1.2.76 h1:3DyzQ30OHt3wiOZVL1se2g1PAPJIU7+tMUyvfMUj1dY= diff --git a/kubernetes/gateway-operator/go.mod b/kubernetes/gateway-operator/go.mod index 83e2e5df8..11a6e1b7c 100644 --- a/kubernetes/gateway-operator/go.mod +++ b/kubernetes/gateway-operator/go.mod @@ -7,7 +7,7 @@ require ( github.com/knadh/koanf/parsers/yaml v1.1.0 github.com/knadh/koanf/providers/confmap v1.0.0 github.com/knadh/koanf/providers/env v1.1.0 - github.com/knadh/koanf/providers/file v1.2.0 + github.com/knadh/koanf/providers/file v1.2.1 github.com/knadh/koanf/v2 v2.3.0 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.0 @@ -23,12 +23,13 @@ require ( ) require ( - dario.cat/mergo v1.0.1 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect @@ -37,11 +38,13 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/containerd/containerd v1.7.29 // indirect - github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect + github.com/containerd/platforms v1.0.0-rc.1 // indirect + github.com/creack/pty v1.1.24 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect @@ -61,6 +64,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect @@ -68,11 +72,12 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect + github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect @@ -82,8 +87,8 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.41 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -99,35 +104,47 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/redis/go-redis/v9 v9.17.3 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.7 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/sdk v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - go.yaml.in/yaml/v3 v3.0.3 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.12.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/grpc v1.79.3 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/apiextensions-apiserver v0.33.3 // indirect @@ -136,7 +153,7 @@ require ( k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/kubectl v0.33.3 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect oras.land/oras-go/v2 v2.6.0 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/kustomize/api v0.19.0 // indirect diff --git a/kubernetes/gateway-operator/go.sum b/kubernetes/gateway-operator/go.sum index b53547fba..7a8ae02c9 100644 --- a/kubernetes/gateway-operator/go.sum +++ b/kubernetes/gateway-operator/go.sum @@ -1,9 +1,7 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= @@ -14,8 +12,7 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= @@ -32,23 +29,21 @@ github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuP github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= -github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= -github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -63,8 +58,7 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -127,8 +121,7 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -144,8 +137,7 @@ github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -153,8 +145,7 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= -github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= -github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -163,8 +154,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -177,8 +168,7 @@ github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5z github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A= github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc= github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY= -github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U= -github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= +github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM= github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -202,11 +192,9 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= -github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA= github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc= github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -249,22 +237,17 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= -github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -277,13 +260,9 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -305,24 +284,17 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGh go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= @@ -333,18 +305,13 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= -go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= +go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -353,30 +320,24 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -384,22 +345,18 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -407,14 +364,11 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -449,8 +403,7 @@ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUy k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/kubectl v0.33.3 h1:r/phHvH1iU7gO/l7tTjQk2K01ER7/OAJi8uFHHyWSac= k8s.io/kubectl v0.33.3/go.mod h1:euj2bG56L6kUGOE/ckZbCoudPwuj4Kud7BR0GzyNiT0= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= diff --git a/platform-api/src/go.mod b/platform-api/src/go.mod index 0aaae691b..bdc4f5a2c 100644 --- a/platform-api/src/go.mod +++ b/platform-api/src/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.29.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/jackc/pgx/v5 v5.9.2 github.com/kelseyhightower/envconfig v1.4.0 github.com/mattn/go-sqlite3 v1.14.41 @@ -50,11 +50,11 @@ require ( github.com/ugorji/go/codec v1.3.1 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.35.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/platform-api/src/go.sum b/platform-api/src/go.sum index 73d1a7658..339a1772f 100644 --- a/platform-api/src/go.sum +++ b/platform-api/src/go.sum @@ -45,8 +45,7 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -119,17 +118,12 @@ go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/sdk/ai/go.mod b/sdk/ai/go.mod index 5ad839093..395a95567 100644 --- a/sdk/ai/go.mod +++ b/sdk/ai/go.mod @@ -13,54 +13,57 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cilium/ebpf v0.11.0 // indirect + github.com/cilium/ebpf v0.16.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect github.com/cockroachdb/redact v1.1.3 // indirect - github.com/containerd/cgroups/v3 v3.0.3 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/containerd/cgroups/v3 v3.0.5 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getsentry/sentry-go v0.12.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/godbus/dbus/v5 v5.0.4 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.1.2 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect - github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect - github.com/klauspost/compress v1.18.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mdlayher/socket v0.5.1 // indirect github.com/milvus-io/milvus-proto/go-api/v2 v2.6.8 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/opencontainers/runtime-spec v1.0.2 // indirect + github.com/opencontainers/runtime-spec v1.2.1 // indirect github.com/panjf2000/ants/v2 v2.11.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.20.5 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/samber/lo v1.27.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -68,21 +71,21 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/tidwall/gjson v1.17.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect + github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect github.com/twpayne/go-geom v1.6.1 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - go.etcd.io/bbolt v1.3.12 // indirect + github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.etcd.io/bbolt v1.4.3 // indirect go.etcd.io/etcd/api/v3 v3.5.23 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.23 // indirect go.etcd.io/etcd/client/v2 v2.305.23 // indirect @@ -91,7 +94,7 @@ require ( go.etcd.io/etcd/raft/v3 v3.5.23 // indirect go.etcd.io/etcd/server/v3 v3.5.23 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/otel v1.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 // indirect @@ -103,13 +106,14 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/sync v0.19.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/time v0.10.0 // indirect + golang.org/x/text v0.35.0 // indirect + golang.org/x/time v0.12.0 // indirect google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect @@ -119,6 +123,6 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apimachinery v0.32.3 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + k8s.io/apimachinery v0.33.3 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/sdk/ai/go.sum b/sdk/ai/go.sum index 910db5c88..8fa257857 100644 --- a/sdk/ai/go.sum +++ b/sdk/ai/go.sum @@ -24,15 +24,13 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= -github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= +github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -45,15 +43,13 @@ github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9D github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= -github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= +github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -78,8 +74,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= -github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= @@ -90,8 +85,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -102,14 +97,15 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -137,8 +133,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -152,15 +147,12 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -182,8 +174,9 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= @@ -228,6 +221,8 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/milvus-io/milvus-proto/go-api/v2 v2.6.8 h1:WTLaqxbwiMuAt0qnpMmnOGnkt7rnj00yrU0rlKnENmk= @@ -238,6 +233,7 @@ github.com/milvus-io/milvus/pkg/v2 v2.6.8 h1:pdFb6BVx8Ek4Ioh7t5VWO6rhZgrFxlulaU1 github.com/milvus-io/milvus/pkg/v2 v2.6.8/go.mod h1:jiC8w9PEhvpN6CkZnG1+hfL5sTmNCiY2HLl/h8d97cw= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -255,8 +251,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= @@ -275,15 +270,11 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= @@ -318,13 +309,11 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -354,8 +343,7 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/twpayne/go-geom v1.6.1 h1:iLE+Opv0Ihm/ABIcvQFGIiFBXd76oBIar9drAwHFhR4= github.com/twpayne/go-geom v1.6.1/go.mod h1:Kr+Nly6BswFsKM5sd31YaoWS5PeDDH2NftJTK7Gd028= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= @@ -375,8 +363,7 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= @@ -385,10 +372,9 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.etcd.io/bbolt v1.3.12 h1:UAxZAIuJqzFwByP19gZC3zd5robK3FOangrGS+Fdczg= -go.etcd.io/bbolt v1.3.12/go.mod h1:Gi2toLZr1jFkuReJm+yEPn7H8wk6ooptePtHYCbCS1g= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/etcd/api/v3 v3.5.23 h1:tQi/RaO6peOhmf0c11miU3RQIYPZmiL3UzG9V+f8g6k= go.etcd.io/etcd/api/v3 v3.5.23/go.mod h1:QP4ZLWROP49Kk/vPLhudxYQcF4ndhMQ1gvJE4rCTAgc= go.etcd.io/etcd/client/pkg/v3 v3.5.23 h1:RzwVV28JgOwGl5TUjA47s9IWxl5qQjM2VqSh8wjFFLM= @@ -405,8 +391,7 @@ go.etcd.io/etcd/server/v3 v3.5.23 h1:2g1hz32pp1TYMI7xUyifR8gXOlUnTBCfixV+uJ8BqRU go.etcd.io/etcd/server/v3 v3.5.23/go.mod h1:sZwt/lLSwYQ/S5aAbzSQX2xNw1WA0Fo5noUCVxVYB4g= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= @@ -436,6 +421,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -447,8 +434,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -477,8 +463,7 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -489,8 +474,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -529,11 +513,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -619,7 +601,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= diff --git a/tests/mock-servers/mock-platform-api/go.mod b/tests/mock-servers/mock-platform-api/go.mod index 25d69b755..2723d911f 100644 --- a/tests/mock-servers/mock-platform-api/go.mod +++ b/tests/mock-servers/mock-platform-api/go.mod @@ -4,9 +4,9 @@ go 1.26.2 require ( github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/jackc/pgx/v5 v5.9.2 - github.com/mattn/go-sqlite3 v1.14.34 + github.com/mattn/go-sqlite3 v1.14.41 ) require ( @@ -15,6 +15,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/text v0.35.0 // indirect ) diff --git a/tests/mock-servers/mock-platform-api/go.sum b/tests/mock-servers/mock-platform-api/go.sum index 2b0b85f1e..6943909fb 100644 --- a/tests/mock-servers/mock-platform-api/go.sum +++ b/tests/mock-servers/mock-platform-api/go.sum @@ -3,8 +3,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -13,8 +12,7 @@ github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= -github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -23,6 +21,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= From f42f674ddb850fd476c0ad20dc78496aaf06b7fe Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Tue, 12 May 2026 01:06:54 +0530 Subject: [PATCH 03/20] Add implementation to map channel name directly to topic when no mapping policy is defined --- .../websocket/broker_api_connector.go | 21 +++++++++----- .../internal/runtime/runtime.go | 29 +++++++++++++++---- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go index cde0ab5b6..5667438bc 100644 --- a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -27,6 +27,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" + "github.com/wso2/api-platform/event-gateway/gateway-runtime/internal/binding" "github.com/wso2/api-platform/event-gateway/gateway-runtime/internal/connectors" ) @@ -396,6 +397,11 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC "chain_key", conn.produceChainKey, "message_size", len(msg.Value)) + // Set default topic to normalized channel name (can be overridden by policies) + if msg.Topic == "" { + msg.Topic = binding.NormalizeTopicSegment(conn.channelName) + } + // Use channel-specific policy chain key via ProcessByChainKey processed, shortCircuited, err := e.processor.ProcessByChainKey(ctx, e.channel.Name, conn.produceChainKey, msg) if err != nil { @@ -408,16 +414,15 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC } // Determine target topic from processed message. - // The topic should be set by policies (e.g., map-topics policy). + // The topic should be set by policies (e.g., map-topic policy) or defaults to normalized channel name. targetTopic := processed.Topic if targetTopic == "" { - // Fallback to default topic. - if len(e.opts.Topics) > 0 { - targetTopic = e.opts.Topics[0] - } else { - slog.Error("No target topic determined for message", "connID", conn.connID) - continue - } + // Fallback to normalized channel name if no policy set a topic + targetTopic = binding.NormalizeTopicSegment(conn.channelName) + slog.Warn("No target topic set by policies, using normalized channel name as default", + "connID", conn.connID, + "channel", conn.channelName, + "topic", targetTopic) } // Publish to Kafka. diff --git a/event-gateway/gateway-runtime/internal/runtime/runtime.go b/event-gateway/gateway-runtime/internal/runtime/runtime.go index 5b58891fe..82b46533a 100644 --- a/event-gateway/gateway-runtime/internal/runtime/runtime.go +++ b/event-gateway/gateway-runtime/internal/runtime/runtime.go @@ -319,11 +319,11 @@ func (r *Runtime) LoadChannels(channelsPath string) error { } // Extract ALL topics (produce + consume) to ensure they exist in Kafka - allChannelTopics := extractAllTopicsFromChannelPolicies(channelDef) + allChannelTopics := extractAllTopicsFromChannelPolicies(channelName, channelDef) allTopics = append(allTopics, allChannelTopics...) // Extract ONLY consume topics for subscription mapping - consumeTopics := extractTopicsFromChannelPolicies(channelDef) + consumeTopics := extractTopicsFromChannelPolicies(channelName, channelDef) for _, topic := range consumeTopics { topicToChannel[topic] = channelName } @@ -802,8 +802,9 @@ func (r *Runtime) buildWebBrokerApiPolicyChains(wbb binding.WebBrokerApiBinding, // extractTopicsFromChannelPolicies extracts Kafka topics to subscribe to from a channel's on_consume policies. // Only extracts topics from map-topic policies with mode="consumeFrom". +// If no policies specify topics, defaults to the channel name itself. // These topics are used for Kafka consumer subscription. -func extractTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) []string { +func extractTopicsFromChannelPolicies(channelName string, channelDef binding.WebBrokerChannelDef) []string { topics := make(map[string]bool) // Use map to deduplicate // ONLY check onConsume policies - these are the topics we subscribe to @@ -817,6 +818,11 @@ func extractTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) [] } } + // If no consumeFrom topics found, default to normalized channel name + if len(topics) == 0 { + topics[binding.NormalizeTopicSegment(channelName)] = true + } + // Convert map to slice result := make([]string, 0, len(topics)) for topic := range topics { @@ -826,9 +832,12 @@ func extractTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) [] } // extractAllTopicsFromChannelPolicies extracts ALL Kafka topics (both produce and consume) from a channel. +// If no policies specify topics, defaults to the channel name itself for both produce and consume. // Used to ensure all necessary topics exist in Kafka before the API starts. -func extractAllTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) []string { +func extractAllTopicsFromChannelPolicies(channelName string, channelDef binding.WebBrokerChannelDef) []string { topics := make(map[string]bool) // Use map to deduplicate + hasProduceTopics := false + hasConsumeTopics := false // Check onProduce policies for produceTo topics for _, policy := range channelDef.OnProduce { @@ -836,6 +845,7 @@ func extractAllTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) if mode, ok := policy.Params["mode"].(string); ok && mode == "produceTo" { if topic, ok := policy.Params["topic"].(string); ok && topic != "" { topics[topic] = true + hasProduceTopics = true } } } @@ -847,11 +857,18 @@ func extractAllTopicsFromChannelPolicies(channelDef binding.WebBrokerChannelDef) if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { if topic, ok := policy.Params["topic"].(string); ok && topic != "" { topics[topic] = true + hasConsumeTopics = true } } } } + // If no topics found from policies, default to normalized channel name + // This enables the channel name to be used directly as the Kafka topic + if !hasProduceTopics && !hasConsumeTopics { + topics[binding.NormalizeTopicSegment(channelName)] = true + } + // Convert map to slice result := make([]string, 0, len(topics)) for topic := range topics { @@ -1133,11 +1150,11 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error } // Extract ALL topics (produce + consume) to ensure they exist in Kafka - allChannelTopics := extractAllTopicsFromChannelPolicies(channelDef) + allChannelTopics := extractAllTopicsFromChannelPolicies(channelName, channelDef) allTopics = append(allTopics, allChannelTopics...) // Extract ONLY consume topics for subscription mapping - consumeTopics := extractTopicsFromChannelPolicies(channelDef) + consumeTopics := extractTopicsFromChannelPolicies(channelName, channelDef) for _, topic := range consumeTopics { topicToChannel[topic] = channelName } From e26b2ef217f3cc31772150f24bcef6015897ad78 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Tue, 12 May 2026 14:53:03 +0530 Subject: [PATCH 04/20] Update WebBroker API Spec --- event-gateway/WEBBROKERAPI.md | 267 ++++++++++-------- gateway/gateway-builder/go.sum | 7 + .../pkg/api/management/webbroker_types.go | 8 +- .../pkg/policyxds/event_channel_translator.go | 24 +- 4 files changed, 179 insertions(+), 127 deletions(-) diff --git a/event-gateway/WEBBROKERAPI.md b/event-gateway/WEBBROKERAPI.md index ec11bf4ed..6b78c4123 100644 --- a/event-gateway/WEBBROKERAPI.md +++ b/event-gateway/WEBBROKERAPI.md @@ -104,21 +104,22 @@ policies: # API-level policies applied to all channels channels: # Channel-specific configurations with per-channel policies "/channel-name": - on_connection_init: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-topic-name - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-topic-name + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-topic-name + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-topic-name ``` ## Example Use Cases @@ -151,23 +152,31 @@ channels: on_consume: [] channels: # Per-channel configurations "/issues": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-repo-issues - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-repo-issues + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-repo-issues + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-repo-issues "/commits": - on_produce: - - name: map-topic - version: v1 - params: + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: mode: produceTo topic: kafka-repo-commits on_consume: @@ -389,26 +398,32 @@ curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ }, "channels": { "/issues": { - "on_produce": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "produceTo", - "topic": "repo-events" + "policies": { + "onConnectionInit": { + "request": [], + "response": [] + }, + "on_produce": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "produceTo", + "topic": "repo-events" + } } - } - ], - "on_consume": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "consumeFrom", - "topic": "repo-events" + ], + "on_consume": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "consumeFrom", + "topic": "repo-events" + } } - } - ] + ] + } } }, "deploymentState": "deployed" @@ -644,18 +659,22 @@ channels: on_consume: [] channels: "/issues": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: repo-events - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: repo-events + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: repo-events + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: repo-events ``` #### 4. Configure Gateway @@ -791,18 +810,22 @@ channels: on_consume: [] channels: "/secure-channel": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: secure-events - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: secure-events + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: secure-events + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: secure-events ``` Test with authentication: @@ -821,31 +844,39 @@ websocat --header "X-API-Key: your-api-key" --header "X-topic: /secure-channel" ```yaml channels: "/issues": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-repo-issues - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-repo-issues + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-repo-issues + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-repo-issues "/commits": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-repo-commits - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-repo-commits + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: kafka-repo-commits + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: kafka-repo-commits ``` Connect to different channels: @@ -907,26 +938,32 @@ curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ }, "channels": { "/issues": { - "on_produce": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "produceTo", - "topic": "produce_issues" + "policies": { + "onConnectionInit": { + "request": [], + "response": [] + }, + "on_produce": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "produceTo", + "topic": "produce_issues" + } } - } - ], - "on_consume": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "consumeFrom", - "topic": "consume_issues" + ], + "on_consume": [ + { + "name": "map-topic", + "version": "v1", + "params": { + "mode": "consumeFrom", + "topic": "consume_issues" + } } - } - ] + ] + } } }, "deploymentState": "deployed" diff --git a/gateway/gateway-builder/go.sum b/gateway/gateway-builder/go.sum index 41ffd4c29..e687fcf9f 100644 --- a/gateway/gateway-builder/go.sum +++ b/gateway/gateway-builder/go.sum @@ -1,18 +1,25 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/wso2/api-platform/sdk/core v0.2.9 h1:3lvAsMlLhy8nNgPL24/UFS/f4sq5e+XpryA4L1PO7dU= github.com/wso2/api-platform/sdk/core v0.2.9/go.mod h1:vgNVzR16g9k5cun3VXZ7wDg8UGbPxsVU2TW8EbRCv0o= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/gateway/gateway-controller/pkg/api/management/webbroker_types.go b/gateway/gateway-controller/pkg/api/management/webbroker_types.go index 5c2c2087b..6c0387e78 100644 --- a/gateway/gateway-controller/pkg/api/management/webbroker_types.go +++ b/gateway/gateway-controller/pkg/api/management/webbroker_types.go @@ -141,8 +141,14 @@ type WebBrokerApiConnectionInitPolicies struct { // WebBrokerApiChannel Channel-specific configuration with policies type WebBrokerApiChannel struct { + // Policies Channel-specific policies + Policies *WebBrokerApiChannelPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` +} + +// WebBrokerApiChannelPolicies defines policies for a specific channel +type WebBrokerApiChannelPolicies struct { // OnConnectionInit Policies applied during WebSocket handshake for this channel - OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` + OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"onConnectionInit,omitempty" yaml:"onConnectionInit,omitempty"` // OnProduce Policies applied when client sends message to broker on this channel OnProduce *[]Policy `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` diff --git a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go index b89604ddf..4648a95ab 100644 --- a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go +++ b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go @@ -210,19 +210,21 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke var channelOnProduce []interface{} var channelOnConsume []interface{} - if channelConfig.OnConnectionInit != nil { - if channelConfig.OnConnectionInit.Request != nil { - channelOnConnectionInitRequest = buildPolicyList(channelConfig.OnConnectionInit.Request) + if channelConfig.Policies != nil { + if channelConfig.Policies.OnConnectionInit != nil { + if channelConfig.Policies.OnConnectionInit.Request != nil { + channelOnConnectionInitRequest = buildPolicyList(channelConfig.Policies.OnConnectionInit.Request) + } + if channelConfig.Policies.OnConnectionInit.Response != nil { + channelOnConnectionInitResponse = buildPolicyList(channelConfig.Policies.OnConnectionInit.Response) + } } - if channelConfig.OnConnectionInit.Response != nil { - channelOnConnectionInitResponse = buildPolicyList(channelConfig.OnConnectionInit.Response) + if channelConfig.Policies.OnProduce != nil { + channelOnProduce = buildPolicyList(channelConfig.Policies.OnProduce) + } + if channelConfig.Policies.OnConsume != nil { + channelOnConsume = buildPolicyList(channelConfig.Policies.OnConsume) } - } - if channelConfig.OnProduce != nil { - channelOnProduce = buildPolicyList(channelConfig.OnProduce) - } - if channelConfig.OnConsume != nil { - channelOnConsume = buildPolicyList(channelConfig.OnConsume) } channels[channelName] = map[string]interface{}{ From e80b96b941fe6fec3d088d1dfce3ed8f621de7a2 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Tue, 12 May 2026 19:34:03 +0530 Subject: [PATCH 05/20] Improve code --- event-gateway/ARCHITECTURE.md | 1177 +++++++++++++++++ event-gateway/build.yaml | 2 +- event-gateway/gateway-runtime/Dockerfile | 2 +- event-gateway/gateway-runtime/Makefile | 6 +- event-gateway/policies/map-topic/go.mod | 5 - .../sample-policies/map-topic/go.mod | 5 + .../map-topic/maptopic.go | 0 .../map-topic/policy-definition.yaml | 0 go.work | 2 +- 9 files changed, 1188 insertions(+), 11 deletions(-) create mode 100644 event-gateway/ARCHITECTURE.md delete mode 100644 event-gateway/policies/map-topic/go.mod create mode 100644 event-gateway/sample-policies/map-topic/go.mod rename event-gateway/{policies => sample-policies}/map-topic/maptopic.go (100%) rename event-gateway/{policies => sample-policies}/map-topic/policy-definition.yaml (100%) diff --git a/event-gateway/ARCHITECTURE.md b/event-gateway/ARCHITECTURE.md new file mode 100644 index 000000000..87acdbb6e --- /dev/null +++ b/event-gateway/ARCHITECTURE.md @@ -0,0 +1,1177 @@ +# Event Gateway Architecture: WebSubApi & WebBrokerApi + +This document provides a comprehensive architectural overview of how **WebSubApi** and **WebBrokerApi** are implemented and fit into the Event Gateway. + +## Architecture Overview + +Both API types follow the same **Receiver → Policy Engine → Broker Driver** architecture with protocol-specific implementations. + +--- + +## 1. Spec Submission → Storage (Controller) + +### Entry Point: REST API +``` +POST /websub-apis → CreateWebSubAPI() +POST /webbroker-apis → CreateWebBrokerApi() +``` + +### Flow +1. **Handler** receives YAML/JSON spec via HTTP +2. **DeploymentService.DeployAPIConfiguration()** processes it: + - Parses spec into typed structs (`WebSubAPI` or `WebBrokerApi`) + - Validates structure + - Stores in **SQLite** as `StoredConfig` + - Returns UUID and deployment status + +### Storage Schema +```go +StoredConfig { + UUID: string // Unique ID + Kind: "WebSubApi" | "WebBrokerApi" + DisplayName: string + Configuration: interface{} // Typed as WebSubAPI or WebBrokerApi + DesiredState: "deployed" | "undeployed" + SourceConfiguration: []byte +} +``` + +**Implementation Files:** +- `gateway/gateway-controller/pkg/api/handlers/websub_api_handler.go` +- `gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go` +- `gateway/gateway-controller/pkg/api/management/generated.go` (WebSubAPI types) +- `gateway/gateway-controller/pkg/api/management/webbroker_types.go` (WebBrokerApi types) + +--- + +## 2. xDS Translation (Controller) + +### Translator Service +**File:** `gateway/gateway-controller/pkg/policyxds/event_channel_translator.go` + +### Functions +- `TranslateWebSubApisToEventChannelConfigs()` +- `TranslateWebBrokerApisToEventChannelConfigs()` + +### Process +1. Fetches all `StoredConfig` entries by Kind +2. Filters for `DesiredState == "deployed"` +3. Converts each into **EventChannelConfig** xDS resource: + ```json + { + "uuid": "...", + "name": "my-api", + "kind": "WebSubApi" | "WebBrokerApi", + "context": "/api/v1", + "version": "1.0.0", + "channels": [...], // Structure differs by Kind + "policies": {...}, // Structure differs by Kind + "receiver": {...}, // Only for WebBrokerApi + "broker-driver": {...} // Only for WebBrokerApi + } + ``` + +### WebSubApi xDS Structure +```json +{ + "kind": "WebSubApi", + "channels": [ + { + "name": "issues", + "policies": { + "subscribe": [], + "inbound": [], + "outbound": [] + } + } + ], + "policies": { + "subscribe": [], // Hub-level auth + "inbound": [], // Webhook receiver validation + "outbound": [] // Delivery transformation/signing + }, + "receiver": { + "type": "websub" + } +} +``` + +### WebBrokerApi xDS Structure +```json +{ + "kind": "WebBrokerApi", + "channels": { + "/issues": { + "policies": { + "onConnectionInit": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + } + } + }, + "policies": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + }, + "receiver": { + "name": "ws-receiver", + "type": "websocket" + }, + "broker-driver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrap.servers": "kafka:9092" + } + } +} +``` + +4. xDS Server pushes to runtime via gRPC stream + +--- + +## 3. Runtime Processing (Event Gateway) + +### xDS Reception +**File:** `event-gateway/gateway-runtime/internal/xdsclient/handler.go` + +### Handler.HandleResources() +1. Receives EventChannelConfig from xDS stream +2. Deserializes JSON payload into `EventChannelResource` +3. Diffs against previous state (add/remove/update) +4. Calls `addBinding()` or `removeBinding()` + +### Binding Conversion + +#### WebSubApi +```go +// In handler.go: toWebSubApiBinding() +WebSubApiBinding { + Kind: "WebSubApi" + Name: string + Context: string + Version: string + Channels: map[string]string // channel-name → Kafka topic + BrokerDriver: BrokerDriverSpec{Type: "kafka", Config: {...}} + Receiver: ReceiverSpec{Type: "websub"} + Policies: {Subscribe: [], Inbound: [], Outbound: []} + ChannelPolicies: map[string]{Subscribe: []} // Per-channel policies +} +``` + +**Implementation File:** `event-gateway/gateway-runtime/internal/binding/types.go` + +#### WebBrokerApi +```go +// In handler.go: toWebBrokerApiBinding() +WebBrokerApiBinding { + Kind: "WebBrokerApi" + Name: string + Context: string // WebSocket path + Receiver: ReceiverSpec{Name: "ws-receiver", Type: "websocket"} + BrokerDriver: BrokerDriverSpec{Name: "kafka-driver", Type: "kafka", ...} + Policies: { + OnConnectionInit: {Request: [], Response: []}, + OnProduce: [], + OnConsume: [] + } + Channels: map[string]WebBrokerChannelDef{ + "/issues": { + OnConnectionInit: {...}, + OnProduce: [], + OnConsume: [] + } + } +} +``` + +**Implementation File:** `event-gateway/gateway-runtime/internal/binding/types.go` + +--- + +## 4. Component Creation (Runtime) + +### Runtime.AddWebSubApiBinding() + +**File:** `event-gateway/gateway-runtime/internal/runtime/runtime.go` + +``` +┌─────────────────────────────────────────────┐ +│ 1. Build Policy Chains │ +│ - Subscribe chain (hub-level + per-chan) │ +│ - Inbound chain (webhook validation) │ +│ - Outbound chain (delivery transform) │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ 2. Create Broker Driver (Kafka) │ +│ - Topics: one per channel + sync topic │ +│ - Producer for webhook ingress │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ 3. Create WebSub Receiver │ +│ - HTTP handlers: /hub, /webhook-receiver │ +│ - Subscription store (in-memory) │ +│ - Consumer manager (per-callback) │ +│ - Deliverer (async retry logic) │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ 4. Register with Hub │ +│ - ChannelBinding with chain keys │ +│ - Channel → Kafka topic mapping │ +└─────────────────────────────────────────────┘ +``` + +**Implementation:** `event-gateway/gateway-runtime/internal/connectors/receiver/websub/connector.go` + +### Runtime.AddWebBrokerApiBinding() + +**File:** `event-gateway/gateway-runtime/internal/runtime/runtime.go` + +``` +┌─────────────────────────────────────────────┐ +│ 1. Build Policy Chains │ +│ API-level: │ +│ - onConnectionInit (request + response) │ +│ - onProduce │ +│ - onConsume │ +│ Per-channel: │ +│ - /issues: onProduce + onConsume chains │ +│ - /commits: onProduce + onConsume chains │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ 2. Extract Topics & Create Broker Driver │ +│ - Parse map-topic policies │ +│ - Extract produceTo + consumeFrom topics │ +│ - Create Kafka driver with all topics │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ 3. Create WebSocket Receiver │ +│ - HTTP handler on context path │ +│ - Metadata: channelNames, chainKeys │ +│ - Upgrade logic with X-topic validation │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ 4. Register with Hub │ +│ - ChannelChainKeys per channel │ +│ - Enables ProcessByChainKey() │ +└─────────────────────────────────────────────┘ +``` + +**Implementation:** `event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go` + +--- + +## 5. Data Flow Architecture + +### WebSubApi Flow (Publisher → Subscribers) + +``` +┌──────────────┐ +│ Publisher │ (HTTP POST webhook) +└──────┬───────┘ + │ + ▼ +┌──────────────────────────────────────────┐ +│ WebSubReceiver │ +│ /api/v1/webhook-receiver │ +└──────┬───────────────────────────────────┘ + │ connectors.Message + ▼ +┌──────────────────────────────────────────┐ +│ Hub.ProcessInbound() │ +│ - Executes inbound policy chain │ +│ - Validates/transforms webhook payload │ +└──────┬───────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────┐ +│ KafkaBrokerDriver.Publish() │ +│ - Writes to topic: api-v1_issues │ +└──────┬───────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────┐ +│ ConsumerManager (per-callback consumer) │ +│ - One consumer per active subscription │ +└──────┬───────────────────────────────────┘ + │ connectors.Message + ▼ +┌──────────────────────────────────────────┐ +│ Hub.ProcessOutbound() │ +│ - Executes outbound policy chain │ +│ - Signs/transforms delivery payload │ +└──────┬───────────────────────────────────┘ + │ + ▼ +┌──────────────┐ +│ Subscriber │ (HTTP POST to callback URL) +└──────────────┘ +``` + +**Key Components:** +- **WebSubReceiver:** HTTP endpoint for data ingress and subscription management +- **ConsumerManager:** Creates dedicated Kafka consumer for each active subscription +- **Deliverer:** Handles async delivery with exponential backoff retry + +### WebBrokerApi Flow (Bidirectional WebSocket ↔ Kafka) + +**Note:** This diagram shows WebSocket + Kafka as an example. The architecture supports any receiver type (eg: SSE) with any broker driver (eg: MQTT, RabbitMQ) through the plugin system (see [Extensibility](#11-extensibility--plugin-architecture)). + +``` +┌───────────────┐ +│ WebSocket │ +│ Client │ +└───────┬───────┘ + │ ws://gateway/api?X-topic=/issues + ▼ +┌──────────────────────────────────────────┐ +│ WebBrokerApiReceiver.handleUpgrade() │ +│ 1. Validate X-topic header │ +│ 2. Apply onConnectionInit.request │ +│ 3. Upgrade to WebSocket │ +│ 4. Apply onConnectionInit.response │ +└──────┬───────────────────────────────────┘ + │ + ├─────────── Inbound (client → broker) ──────────┐ + │ │ + ▼ │ +┌──────────────────────────────────────────┐ │ +│ brokerApiConnection.inboundLoop() │ │ +│ - Read WebSocket frames │ │ +└──────┬───────────────────────────────────┘ │ + │ connectors.Message │ + ▼ │ +┌──────────────────────────────────────────┐ │ +│ Hub.ProcessByChainKey(produceChainKey) │ │ +│ - API-level on_produce │ │ +│ - Channel-level on_produce (/issues) │ │ +│ - map-topic policy sets kafka.topic │ │ +└──────┬───────────────────────────────────┘ │ + │ │ + ▼ │ +┌──────────────────────────────────────────┐ │ +│ KafkaBrokerDriver.Publish() │ │ +│ - Writes to: produce_issues │ │ +└──────────────────────────────────────────┘ │ + │ + ├─────────── Outbound (broker → client) ─────────┘ + │ + ▼ +┌──────────────────────────────────────────┐ +│ Kafka Consumer (unique group per conn) │ +│ - Reads from: consume_issues │ +└──────┬───────────────────────────────────┘ + │ connectors.Message + ▼ +┌──────────────────────────────────────────┐ +│ Hub.ProcessByChainKey(consumeChainKey) │ +│ - API-level on_consume │ +│ - Channel-level on_consume (/issues) │ +└──────┬───────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────┐ +│ brokerApiConnection.outboundLoop() │ +│ - Write WebSocket frames │ +└──────────────────────────────────────────┘ +``` + +**Key Components:** +- **brokerApiConnection:** Per-connection state with dedicated Kafka consumer/producer +- **Consumer Group:** `{prefix}-ws-{uuid}` ensures per-connection isolation +- **Bidirectional Channels:** Inbound (client→Kafka) and Outbound (Kafka→client) Go channels + +--- + +## 6. Policy Engine Integration + +### Policy Chain Structure +Both API types use the same policy engine with different enforcement points. + +**Key Insight: Policies are fully interchangeable between WebSubApi and WebBrokerApi.** The same policy (e.g., `api-key-auth`, `transform-payload-case`, `map-topic`) can be used in either API type. The policy engine treats all policies identically regardless of which API type invokes them. + +#### What Makes Policies Interchangeable? + +1. **Unified Policy Engine:** Both API types use the same `engine.Engine` instance +2. **Same Execution Method:** Both call `engine.ExecuteRequestHeaderPolicies()` and `engine.ExecuteRequestBodyPolicies()` +3. **Common Message Format:** Both work with `connectors.Message` structure +4. **No API-Type Restrictions:** Policy definitions have no `applyTo` or API-type constraints +5. **Same Results Processing:** Both process `RequestHeaderResult` and `RequestBodyResult` identically + +#### The Only Differences: + +| Aspect | WebSubApi | WebBrokerApi | +|--------|-----------|--------------| +| **Enforcement Point Names** | subscribe, inbound, outbound | onConnectionInit, onProduce, onConsume | +| **Chain Key Format** | `{api}-subscribe`, `{api}-inbound`, `{api}-outbound` | `{api}-on_connection_init_req`, `{api}-on_produce`, etc. | +| **When Applied** | Subscription requests, webhook ingress, callback delivery | WebSocket upgrade, client→broker, broker→client | +| **Hub Method** | `ProcessSubscribe()`, `ProcessInbound()`, `ProcessOutbound()` | `ProcessByChainKey()` with specific chain keys | + +**Example: The Same Policy in Both API Types** + +```yaml +# In WebSubApi - validating inbound webhook data +spec: + receiver: + policies: + - name: json-schema-validator + version: v1 + params: + schema: {...} + +# In WebBrokerApi - validating inbound client messages +spec: + policies: + on_produce: + - name: json-schema-validator + version: v1 + params: + schema: {...} +``` + +Both use the **exact same policy** (`json-schema-validator`), just at different enforcement points! + +#### WebSubApi Policy Enforcement Points + +| Phase | Purpose | When Applied | +|-------|---------|--------------| +| **Subscribe** | Authentication/authorization | During subscription request (POST /hub) | +| **Inbound** | Validate/transform incoming data | When publisher sends webhook data | +| **Outbound** | Sign/transform outgoing delivery | Before delivering to subscriber callback | + +**Implementation:** `event-gateway/gateway-runtime/internal/hub/hub.go` +- `Hub.ProcessSubscribe()` - Subscribe phase +- `Hub.ProcessInbound()` - Inbound phase +- `Hub.ProcessOutbound()` - Outbound phase + +#### WebBrokerApi Policy Enforcement Points + +| Phase | Purpose | When Applied | +|-------|---------|--------------| +| **onConnectionInit (request)** | Authenticate upgrade request | Before WebSocket upgrade | +| **onConnectionInit (response)** | Modify upgrade response | After successful upgrade | +| **onProduce** | Validate/route client messages | Client → Kafka direction | +| **onConsume** | Transform broker messages | Kafka → Client direction | + +**Implementation:** `event-gateway/gateway-runtime/internal/hub/hub.go` +- `Hub.ProcessByChainKey()` - All phases using specific chain keys + +### Hub Orchestration + +The Hub acts as the central message router and policy orchestrator: + +```go +// WebSubApi +msg → hub.engine.ExecuteRequestPolicies(inboundChain) → result + +// WebBrokerApi +msg → hub.engine.ExecuteRequestPolicies(chainKey) → result +``` + +**Chain Building:** Policies are compiled into chains during binding registration: +- API-level policies apply to all channels +- Channel-level policies apply only to specific channels +- Chains are stored with unique keys in the policy engine + +--- + +## 7. Key Differences + +| Aspect | WebSubApi | WebBrokerApi | +|--------|-----------|--------------| +| **Protocol** | HTTP Webhooks (WebSub standard) | Pluggable (WebSocket, SSE, etc.) | +| **Broker** | Pluggable (Kafka default) | Pluggable (Kafka, MQTT, RabbitMQ, etc.) | +| **Direction** | Unidirectional (pub → sub) | Bidirectional (client ↔ broker) | +| **Connection Model** | Request/response per webhook | Persistent connection | +| **Subscription Model** | Hub manages subscriptions | Per-connection isolation | +| **Kafka Consumer** | One per callback URL | One per connection (for Kafka) | +| **Consumer Group** | `{api}-{callback-hash}` | `{prefix}-{protocol}-{uuid}` | +| **Topics** | One per channel + sync topic | Separate produce/consume topics per channel | +| **Receiver Paths** | `/hub`, `/webhook-receiver` | Configurable via `context` field | +| **Policy Points** | 3 (subscribe, inbound, outbound) | 3 (onConnectionInit, onProduce, onConsume) | +| **State Management** | Subscription store + Kafka sync topic | Per-connection state only | +| **Delivery** | Async with retry (exponential backoff) | Synchronous over connection | +| **Use Case** | Event distribution to webhooks | Protocol mediation / real-time streaming | +| **Extensibility** | Fixed to WebSub protocol | Any streaming protocol via receiver plugins | + +**Note:** While WebSubApi is specialized for the WebSub protocol, WebBrokerApi is designed for **protocol mediation** between any streaming protocol and any message broker through its plugin architecture. + +--- + +## 8. Component Lifecycle + +### Deployment Flow +``` +Controller: + 1. Spec submitted via REST API + 2. Stored in SQLite (StoredConfig) + 3. Translated to EventChannelConfig xDS + 4. Pushed to runtime via gRPC stream + +Runtime: + 1. xDS received and deserialized + 2. Binding created (WebSubApiBinding or WebBrokerApiBinding) + 3. Components instantiated: + - Policy chains built + - Broker driver created + - Receiver created + 4. Registered with Hub + 5. HTTP handlers activated +``` + +### Teardown Flow +``` +Controller: + 1. API deleted or undeployed + 2. xDS deletion marker sent to runtime + +Runtime: + 1. xDS delete received + 2. Hub deregisters binding + 3. Receiver stops (closes connections) + 4. Broker driver closes + 5. Kafka consumers cleanup + 6. HTTP handlers removed +``` + +### Dynamic Updates +The xDS-based architecture enables zero-downtime updates: +- New configuration → new binding created +- Old binding continues serving existing connections +- New connections use new binding +- Old binding removed when connections drain + +--- + +## 9. Configuration Examples + +### WebSubApi Configuration + +```yaml +apiVersion: wso2.com/v1 +kind: WebSubApi +metadata: + name: github-events +spec: + context: /github/webhooks + version: v1 + hub: + policies: + - name: api-key-auth + version: v1 + params: + in: header + name: X-API-Key + channels: + - name: issues + policies: + - name: json-schema-validator + version: v1 + params: + schema: {...} + - name: pull_requests + receiver: + policies: + - name: hmac-signature-validator + version: v1 + params: + algorithm: sha256 + header: X-Hub-Signature-256 + delivery: + policies: + - name: hmac-signature-signer + version: v1 + params: + algorithm: sha256 + header: X-Hub-Signature +``` + +**Resulting Topics:** +- `github-webhooks-v1_issues` - Issue events +- `github-webhooks-v1_pull_requests` - PR events +- `_sync_github-webhooks-v1` - Subscription state sync + +### WebBrokerApi Configuration + +```yaml +apiVersion: wso2.com/v1 +kind: WebBrokerApi +metadata: + name: realtime-events +spec: + context: /ws/events + version: v1 + receiver: + name: ws-receiver + type: websocket + broker-driver: + name: kafka-driver + type: kafka + properties: + bootstrap.servers: kafka:9092 + policies: + on_connection_init: + request: + - name: api-key-auth + version: v1 + params: + in: header + name: X-API-Key + on_produce: [] + on_consume: [] + channels: + "/issues": + policies: + onConnectionInit: + request: [] + response: [] + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: produce_issues + on_consume: + - name: map-topic + version: v1 + params: + mode: consumeFrom + topic: consume_issues +``` + +**Resulting Topics:** +- `produce_issues` - Client writes +- `consume_issues` - Client reads + +**WebSocket Connection:** +```bash +websocat --header "X-API-Key: secret" --header "X-topic: /issues" ws://gateway:8081/ws/events/v1 +``` + +--- + +## 10. Implementation File Reference + +### Controller (Gateway Controller) + +| Component | File Path | +|-----------|-----------| +| WebSubApi Types | `gateway/gateway-controller/pkg/api/management/generated.go` | +| WebBrokerApi Types | `gateway/gateway-controller/pkg/api/management/webbroker_types.go` | +| WebSubApi Handler | `gateway/gateway-controller/pkg/api/handlers/websub_api_handler.go` | +| WebBrokerApi Handler | `gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go` | +| xDS Translator | `gateway/gateway-controller/pkg/policyxds/event_channel_translator.go` | +| Storage | `gateway/gateway-controller/pkg/storage/` | + +### Runtime (Event Gateway) + +| Component | File Path | +|-----------|-----------| +| Binding Types | `event-gateway/gateway-runtime/internal/binding/types.go` | +| xDS Handler | `event-gateway/gateway-runtime/internal/xdsclient/handler.go` | +| Runtime Core | `event-gateway/gateway-runtime/internal/runtime/runtime.go` | +| Hub | `event-gateway/gateway-runtime/internal/hub/hub.go` | +| WebSub Receiver | `event-gateway/gateway-runtime/internal/connectors/receiver/websub/connector.go` | +| WebBroker Receiver | `event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go` | +| Kafka Driver | `event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/endpoint.go` | +| Policy Engine | `gateway/gateway-runtime/policy-engine/` | + +--- + +--- + +## 11. Policy Interchangeability + +### Are Policies the Same Between WebSubApi and WebBrokerApi? + +**Yes! Policies are fully interchangeable.** A policy that works in WebSubApi will work in WebBrokerApi and vice versa. The policy engine treats all policies identically regardless of which API type invokes them. + +### How Policy Execution Works (Unified for Both API Types) + +Both WebSubApi and WebBrokerApi use the **exact same policy engine** (`engine.Engine`) and follow the same execution flow: + +```go +// In Hub.go - Same for both API types +msg → engine.ExecuteRequestHeaderPolicies(chainKey) → RequestHeaderResult + → engine.ExecuteRequestBodyPolicies(chainKey) → RequestBodyResult + → Apply results to msg +``` + +**Implementation:** +- **WebSubApi:** Calls `Hub.ProcessInbound()`, `Hub.ProcessOutbound()`, `Hub.ProcessSubscribe()` +- **WebBrokerApi:** Calls `Hub.ProcessByChainKey()` with specific chain keys + +Both ultimately call the same engine methods: +- `engine.ExecuteRequestHeaderPolicies()` +- `engine.ExecuteRequestBodyPolicies()` + +### What Makes Them Interchangeable? + +| Component | WebSubApi | WebBrokerApi | Same? | +|-----------|-----------|--------------|-------| +| **Policy Engine** | `engine.Engine` | `engine.Engine` | ✅ Same instance | +| **Message Format** | `connectors.Message` | `connectors.Message` | ✅ Same struct | +| **Policy Registry** | `registry.PolicyRegistry` | `registry.PolicyRegistry` | ✅ Same registry | +| **Execution Methods** | `ExecuteRequest*Policies()` | `ExecuteRequest*Policies()` | ✅ Same methods | +| **Result Types** | `RequestHeaderResult`, `RequestBodyResult` | `RequestHeaderResult`, `RequestBodyResult` | ✅ Same types | +| **Policy Definitions** | `policy-definition.yaml` | `policy-definition.yaml` | ✅ Same format | +| **Policy Factories** | `policy.PolicyFactory` | `policy.PolicyFactory` | ✅ Same interface | + +### The Only Difference: Enforcement Point Naming + +The **only** difference is the naming convention for enforcement points: + +| WebSubApi Enforcement Point | WebBrokerApi Enforcement Point | Purpose | +|----------------------------|-------------------------------|---------| +| `on_subscription` | `on_connection_init.request` | Authenticate/authorize initial request | +| N/A | `on_connection_init.response` | Modify initial response | +| `on_message_received` (inbound) | `on_produce` | Validate/transform messages going TO broker | +| `on_message_delivery` (outbound) | `on_consume` | Validate/transform messages FROM broker | +| `on_unsubscription` | N/A | Handle unsubscription (WebSub-specific) | + +### Example: Using the Same Policies in Both API Types + +#### Authentication Policy Example + +**WebSubApi:** +```yaml +apiVersion: wso2.com/v1 +kind: WebSubApi +metadata: + name: secure-webhooks +spec: + hub: + policies: + - name: api-key-auth # ← Same policy + version: v1 + params: + in: header + name: X-API-Key +``` + +**WebBrokerApi:** +```yaml +apiVersion: wso2.com/v1 +kind: WebBrokerApi +metadata: + name: secure-websocket +spec: + policies: + on_connection_init: + request: + - name: api-key-auth # ← Same policy + version: v1 + params: + in: header + name: X-API-Key +``` + +#### Transformation Policy Example + +**WebSubApi:** +```yaml +spec: + receiver: + policies: + - name: transform-payload-case # ← Same policy + version: v1 + params: + targetCase: lowercase +``` + +**WebBrokerApi:** +```yaml +spec: + policies: + on_produce: + - name: transform-payload-case # ← Same policy + version: v1 + params: + targetCase: lowercase +``` + +#### Validation Policy Example + +**WebSubApi:** +```yaml +spec: + hub: + channels: + - name: issues + policies: + - name: json-schema-validator # ← Same policy + version: v1 + params: + schema: + type: object + required: [title, body] +``` + +**WebBrokerApi:** +```yaml +spec: + channels: + "/issues": + policies: + on_produce: + - name: json-schema-validator # ← Same policy + version: v1 + params: + schema: + type: object + required: [title, body] +``` + +### Policy Definition Structure (Agnostic to API Type) + +A policy definition has **no API-type restrictions**: + +```yaml +# gateway/policies/my-policy/policy-definition.yaml +name: my-custom-policy +version: v1.0.0 +displayName: My Custom Policy +description: This policy works in both WebSubApi and WebBrokerApi + +parameters: + type: object + properties: + someParam: + type: string + description: A parameter that works everywhere + +systemParameters: + type: object + properties: {} +``` + +**No `applyTo` or `apiType` field exists!** Policies are universal. + +### When Would a Policy NOT Work? + +A policy might not be semantically appropriate (but will still technically execute) if: + +1. **Protocol-specific logic:** A policy that expects WebSocket-specific headers in a WebSub HTTP POST +2. **Broker-specific features:** A policy that uses Kafka-specific features (like partition keys) with an MQTT broker +3. **Timing mismatch:** Applying a subscription validation policy at the message delivery phase + +**But these are semantic/logical issues, not technical restrictions.** The policy engine will still execute them. + +### Benefits of This Design + +✅ **Write Once, Use Everywhere:** Develop a policy once, use it in any API type +✅ **Consistent Behavior:** Same policy behaves identically across API types +✅ **Simplified Testing:** Test policies independently of API types +✅ **Reusable Libraries:** Build policy libraries that work universally +✅ **Easier Migration:** Move policies between API types without modification +✅ **Future-Proof:** New API types automatically support existing policies + +--- + +## 12. Extensibility & Plugin Architecture + +### Pluggable Receivers and Broker Drivers + +The WebBrokerApi implementation uses a **plugin/registry pattern** that allows adding new receivers and broker drivers without modifying the core runtime. While WebSocket and Kafka are the default implementations, the architecture is designed to support any streaming protocol and message broker. + +### Receiver Interface + +**File:** `event-gateway/gateway-runtime/internal/connectors/types.go` + +Any protocol can be a receiver by implementing: + +```go +type Receiver interface { + Start(ctx context.Context) error + Stop(ctx context.Context) error +} +``` + +**Currently Implemented:** +- `websub` - HTTP WebSub (WebHooks) +- `websocket` - WebSocket receiver for legacy single-channel protocol mediation + - Simple 1:1 passthrough between WebSocket client and broker + - Single topic per API + - Uses `ProcessInbound()` policy enforcement only +- `websocket-broker-api` - WebSocket receiver for multi-channel WebBrokerApi + - Supports multiple channels per API via `X-topic` header routing + - Per-channel policy chains (onConnectionInit, onProduce, onConsume) + - Separate produce/consume topics per channel + - Designed for the WebBrokerApi specification + +**Future Possibilities:** +- `sse` - Server-Sent Events +- `grpc-stream` - gRPC bidirectional streaming +- `http-long-poll` - HTTP Long Polling +- `amqp` - AMQP protocol +- `mqtt-ws` - MQTT over WebSocket + +### BrokerDriver Interface + +**File:** `event-gateway/gateway-runtime/internal/connectors/types.go` + +Any message broker can be a broker driver by implementing: + +```go +type BrokerDriver interface { + Publish(ctx context.Context, topic string, msg *Message) error + Subscribe(groupID string, topics []string, handler MessageHandler) (Receiver, error) + TopicExists(ctx context.Context, topic string) (bool, error) + EnsureTopics(ctx context.Context, topics []string) error + DeleteTopics(ctx context.Context, topics []string) error + Close() error +} +``` + +**Currently Implemented:** +- `kafka` - Apache Kafka via franz-go + +**Future Possibilities:** +- `mqtt` - MQTT broker +- `rabbitmq` - RabbitMQ +- `pulsar` - Apache Pulsar +- `redis` - Redis Streams +- `nats` - NATS JetStream +- `aws-sqs` - AWS SQS +- `azure-servicebus` - Azure Service Bus + +### Plugin Registration + +**File:** `event-gateway/gateway-runtime/cmd/event-gateway/plugins.go` + +New types are registered at startup: + +```go +func registerConnectors(registry *connectors.Registry, cfg *config.Config) { + // Register broker drivers + registry.RegisterBrokerDriver("kafka", func(cfg map[string]interface{}) (BrokerDriver, error) { + return kafka.NewBrokerDriver(brokers) + }) + + registry.RegisterBrokerDriver("mqtt", func(cfg map[string]interface{}) (BrokerDriver, error) { + return mqtt.NewBrokerDriver(cfg) // Future implementation + }) + + // Register receivers + registry.RegisterReceiver("websocket-broker-api", func(cfg ReceiverConfig) (Receiver, error) { + return websocket.NewBrokerApiReceiver(cfg, opts) + }) + + registry.RegisterReceiver("sse", func(cfg ReceiverConfig) (Receiver, error) { + return sse.NewBrokerApiReceiver(cfg, opts) // Future implementation + }) +} +``` + +### Adding a New Receiver (e.g., SSE) + +**1. Implement the Receiver interface:** + +```go +// event-gateway/gateway-runtime/internal/connectors/receiver/sse/broker_api_connector.go +package sse + +type SSEBrokerApiReceiver struct { + channel connectors.ChannelInfo + processor connectors.MessageProcessor + brokerDriver connectors.BrokerDriver + connections map[string]*sseConnection +} + +func NewBrokerApiReceiver(cfg connectors.ReceiverConfig, opts Options) (connectors.Receiver, error) { + receiver := &SSEBrokerApiReceiver{ + channel: cfg.Channel, + processor: cfg.Processor, + brokerDriver: cfg.BrokerDriver, + connections: make(map[string]*sseConnection), + } + + // Register HTTP handler for SSE endpoint + cfg.Mux.HandleFunc(cfg.Channel.Context, receiver.handleSSE) + + return receiver, nil +} + +func (r *SSEBrokerApiReceiver) Start(ctx context.Context) error { + // Initialize SSE receiver + return nil +} + +func (r *SSEBrokerApiReceiver) Stop(ctx context.Context) error { + // Close all SSE connections + return nil +} +``` + +**2. Register in plugins.go:** + +```go +registry.RegisterReceiver("sse", func(cfg connectors.ReceiverConfig) (connectors.Receiver, error) { + return sse.NewBrokerApiReceiver(cfg, sse.BrokerApiOptions{ + Port: config.Server.HTTPPort, + ConsumerGroupPrefix: config.Kafka.ConsumerGroupPrefix, + Topics: cfg.Channel.Topics, + }) +}) +``` + +**3. Use in API spec:** + +```yaml +apiVersion: wso2.com/v1 +kind: WebBrokerApi +metadata: + name: sse-events +spec: + context: /sse/events + version: v1 + receiver: + name: sse-receiver + type: sse # ← New receiver type + properties: + retry: 3000 + broker-driver: + name: kafka-driver + type: kafka + properties: + bootstrap.servers: kafka:9092 +``` + +### Adding a New Broker Driver (e.g., MQTT) + +**1. Implement the BrokerDriver interface:** + +```go +// event-gateway/gateway-runtime/internal/connectors/brokerdriver/mqtt/endpoint.go +package mqtt + +type MQTTBrokerDriver struct { + client mqtt.Client + brokers []string +} + +func NewBrokerDriver(brokers []string) (connectors.BrokerDriver, error) { + // Initialize MQTT client + return &MQTTBrokerDriver{brokers: brokers}, nil +} + +func (d *MQTTBrokerDriver) Publish(ctx context.Context, topic string, msg *connectors.Message) error { + // Publish to MQTT broker + return d.client.Publish(topic, 0, false, msg.Value) +} + +func (d *MQTTBrokerDriver) Subscribe(groupID string, topics []string, handler MessageHandler) (Receiver, error) { + // Create MQTT subscriber + return newMQTTSubscriber(d.client, topics, handler) +} + +// Implement other interface methods... +``` + +**2. Register in plugins.go:** + +```go +registry.RegisterBrokerDriver("mqtt", func(cfg map[string]interface{}) (BrokerDriver, error) { + brokers := extractBrokers(cfg) + return mqtt.NewBrokerDriver(brokers) +}) +``` + +**3. Use in API spec:** + +```yaml +apiVersion: wso2.com/v1 +kind: WebBrokerApi +metadata: + name: mqtt-events +spec: + context: /ws/mqtt + version: v1 + receiver: + name: ws-receiver + type: websocket + broker-driver: + name: mqtt-driver + type: mqtt # ← New broker driver type + properties: + brokers: + - tcp://mqtt-broker:1883 + client_id: event-gateway +``` + +### Example: SSE ↔ MQTT + +Combining new receiver and broker driver types: + +```yaml +apiVersion: wso2.com/v1 +kind: WebBrokerApi +metadata: + name: iot-events +spec: + context: /sse/iot + version: v1 + receiver: + name: sse-receiver + type: sse # ← Server-Sent Events + broker-driver: + name: mqtt-driver + type: mqtt # ← MQTT broker + properties: + brokers: + - tcp://mqtt-broker:1883 + channels: + "/sensors": + policies: + on_produce: + - name: map-topic + version: v1 + params: + mode: produceTo + topic: iot/sensors/data +``` + +This enables: **SSE Client → Policy Engine → MQTT Broker** + +### Benefits of This Architecture + +- ✅ **Zero core changes** - Add new types without modifying runtime or controller +- ✅ **Type safety** - Compile-time checks via Go interfaces +- ✅ **Dynamic loading** - Runtime selects implementation based on spec +- ✅ **Configuration flexibility** - Each type gets custom properties +- ✅ **Testability** - Mock receivers/drivers for testing +- ✅ **Community extensibility** - Third parties can add new connectors + +--- + +## Summary + +Both **WebSubApi** and **WebBrokerApi** leverage the same foundational architecture: + +1. **Controller** manages API lifecycle via REST API and SQLite storage +2. **xDS Protocol** distributes configurations to runtime instances +3. **Runtime** instantiates protocol-specific receivers and broker drivers via **plugin registry** +4. **Hub** orchestrates policy execution and message routing +5. **Policy Engine** enforces security and transformation at defined points + +The architecture ensures: +- ✅ **Consistent patterns** across both API types +- ✅ **Dynamic configuration** via xDS for zero-downtime updates +- ✅ **Protocol flexibility** through pluggable receivers (WebSocket, SSE, etc.) +- ✅ **Broker flexibility** through pluggable drivers (Kafka, MQTT, RabbitMQ, etc.) +- ✅ **Policy enforcement** at appropriate protocol lifecycle points +- ✅ **Extensibility** without modifying core runtime code diff --git a/event-gateway/build.yaml b/event-gateway/build.yaml index 6efb4c649..ddab3d7e7 100644 --- a/event-gateway/build.yaml +++ b/event-gateway/build.yaml @@ -7,7 +7,7 @@ policies: - name: api-key-auth gomodule: github.com/wso2/gateway-controllers/policies/api-key-auth@v1.0.1 - name: map-topic - filePath: ./policies/map-topic + filePath: ./sample-policies/map-topic - name: advanced-ratelimit gomodule: github.com/wso2/gateway-controllers/policies/advanced-ratelimit@v1 - name: analytics-header-filter diff --git a/event-gateway/gateway-runtime/Dockerfile b/event-gateway/gateway-runtime/Dockerfile index 5691749db..a0ae9b25e 100644 --- a/event-gateway/gateway-runtime/Dockerfile +++ b/event-gateway/gateway-runtime/Dockerfile @@ -86,7 +86,7 @@ COPY --from=sdk-core . /api-platform/sdk/core COPY --from=common . /api-platform/common COPY --from=analytics . /api-platform/gateway/system-policies/analytics COPY --from=policy-engine . /api-platform/gateway/gateway-runtime/policy-engine -COPY --from=policies . /api-platform/event-gateway/policies +COPY --from=policies . /api-platform/event-gateway/sample-policies COPY --from=target build.yaml /api-platform/event-gateway/build.yaml RUN mkdir -p /api-platform/output diff --git a/event-gateway/gateway-runtime/Makefile b/event-gateway/gateway-runtime/Makefile index 5a8bd17f1..87157d4f6 100644 --- a/event-gateway/gateway-runtime/Makefile +++ b/event-gateway/gateway-runtime/Makefile @@ -89,7 +89,7 @@ build-docker: ## Build Docker image --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ - --build-context policies=../policies \ + --build-context policies=../sample-policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ @@ -110,7 +110,7 @@ build-debug: ## Build Docker image with Delve remote debugger (port 2345) --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ - --build-context policies=../policies \ + --build-context policies=../sample-policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ @@ -131,7 +131,7 @@ build-and-push-multiarch: ## Build and push Docker image for multiple architectu --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ - --build-context policies=../policies \ + --build-context policies=../sample-policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ diff --git a/event-gateway/policies/map-topic/go.mod b/event-gateway/policies/map-topic/go.mod deleted file mode 100644 index 0aef3fdb5..000000000 --- a/event-gateway/policies/map-topic/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/wso2/api-platform/event-gateway/policies/map-topic - -go 1.26.2 - -require github.com/wso2/api-platform/sdk/core v0.2.9 diff --git a/event-gateway/sample-policies/map-topic/go.mod b/event-gateway/sample-policies/map-topic/go.mod new file mode 100644 index 000000000..15d3586b8 --- /dev/null +++ b/event-gateway/sample-policies/map-topic/go.mod @@ -0,0 +1,5 @@ +module github.com/wso2/api-platform/event-gateway/sample-policies/map-topic + +go 1.26.2 + +require github.com/wso2/api-platform/sdk/core v0.2.9 diff --git a/event-gateway/policies/map-topic/maptopic.go b/event-gateway/sample-policies/map-topic/maptopic.go similarity index 100% rename from event-gateway/policies/map-topic/maptopic.go rename to event-gateway/sample-policies/map-topic/maptopic.go diff --git a/event-gateway/policies/map-topic/policy-definition.yaml b/event-gateway/sample-policies/map-topic/policy-definition.yaml similarity index 100% rename from event-gateway/policies/map-topic/policy-definition.yaml rename to event-gateway/sample-policies/map-topic/policy-definition.yaml diff --git a/go.work b/go.work index 785ca50fd..f256f58ce 100644 --- a/go.work +++ b/go.work @@ -6,7 +6,7 @@ use ( ./common ./event-gateway/gateway-builder ./event-gateway/gateway-runtime - ./event-gateway/policies/map-topic + ./event-gateway/sample-policies/map-topic ./event-gateway/webhook-listener ./gateway/gateway-builder ./gateway/gateway-controller From 5bd457b1700be7761e5dd1be9de7427d40b86749 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Tue, 12 May 2026 21:49:02 +0530 Subject: [PATCH 06/20] Delete the duplicated config.toml --- event-gateway/WEBBROKERAPI.md | 27 ++++++++++++++++--- .../configs/gateway-controller/config.toml | 23 ---------------- 2 files changed, 23 insertions(+), 27 deletions(-) delete mode 100644 event-gateway/configs/gateway-controller/config.toml diff --git a/event-gateway/WEBBROKERAPI.md b/event-gateway/WEBBROKERAPI.md index 6b78c4123..ba1216839 100644 --- a/event-gateway/WEBBROKERAPI.md +++ b/event-gateway/WEBBROKERAPI.md @@ -327,6 +327,25 @@ The event gateway supports two configuration modes: - [Go 1.24+](https://go.dev/dl/) (for building from source) - [Kafka](https://kafka.apache.org/) (provided via Docker Compose) +### Setup: Copy Shared Configuration + +The event-gateway reuses the gateway-controller configuration from the main gateway. Before starting, copy the shared config: + +```bash +# From the api-platform root directory +cp gateway/configs/config.toml event-gateway/configs/gateway-controller/config.toml +``` + +This config contains: +- REST API authentication credentials (default: `admin:admin`) +- Gateway identification and control plane settings +- Logging configuration + +**Note:** Whenever you update credentials or settings in `gateway/configs/config.toml`, remember to sync it to the event-gateway: +```bash +cp gateway/configs/config.toml event-gateway/configs/gateway-controller/config.toml +``` + ### Option 1: Using Docker Compose (Recommended) This is the easiest way to test protocol mediation with all dependencies. @@ -1231,8 +1250,8 @@ Note the `topics=[consume_issues]` - this confirms the consumer only subscribes - Check for policy errors during connection init **Gateway container exits with "config.toml is a directory" error:** -- Ensure `configs/gateway-controller/config.toml` is a file, not a directory -- If you accidentally created it as a directory: `rm -rf configs/gateway-controller/config.toml` and recreate as a file +- The event-gateway reuses the main gateway config from `../gateway/configs/config.toml` +- Ensure that file exists and is not a directory - Restart services: `docker compose up -d` **Event gateway exits with "TLS certificate file does not exist":** @@ -1326,9 +1345,9 @@ docker exec -it event-gateway-kafka-1 kafka-console-producer \ **Config File Locations:** -- Gateway config: `gateway-runtime/configs/config.toml` +- Runtime config: `gateway-runtime/configs/config.toml` - Channels config: `gateway-runtime/configs/channels.yaml` -- Controller config: `configs/gateway-controller/config.toml` +- Controller config: `../gateway/configs/config.toml` (shared with main gateway) ### Sample WebBrokerApi Configurations diff --git a/event-gateway/configs/gateway-controller/config.toml b/event-gateway/configs/gateway-controller/config.toml deleted file mode 100644 index 9aea2ef6f..000000000 --- a/event-gateway/configs/gateway-controller/config.toml +++ /dev/null @@ -1,23 +0,0 @@ -# Gateway Controller Configuration - -# TODO We will have to move this to a common place. Separate controller config file is not gonna work - -[controller.server] -gateway_id = "event-gateway-id" - -[controller.logging] -level = "info" - -[controller.controlplane] -insecure_skip_verify = true -gateway_name = "default" -apim_oauth2_client_id = "your-client-id" -apim_oauth2_client_secret = "your-client-secret" - -[controller.auth.basic] -enabled = true - -[[controller.auth.basic.users]] -username = "admin" -password = "admin" -roles = ["admin"] From 61545409a682826984016dca2ce2d8015fbcff2a Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 03:43:57 +0530 Subject: [PATCH 07/20] Update spec and add it to management-openapi.yaml --- docs/rest-apis/gateway/README.md | 12 + docs/rest-apis/gateway/schemas.md | 846 +++++++++++++++++- .../gateway/webbroker-api-management.md | 518 +++++++++++ .../gateway/websub-api-management.md | 364 +++++++- .../gateway-runtime/internal/binding/types.go | 7 + .../websocket/broker_api_connector.go | 46 +- .../internal/runtime/runtime.go | 88 +- .../internal/xdsclient/handler.go | 54 +- .../api/management-openapi.yaml | 479 ++++++++++ .../gateway-controller/cmd/controller/main.go | 11 - .../pkg/api/handlers/webbroker_api_handler.go | 4 +- .../pkg/api/management/generated.go | 828 ++++++++++++----- .../pkg/api/management/webbroker_types.go | 158 ---- .../pkg/policyxds/event_channel_translator.go | 24 +- 14 files changed, 2951 insertions(+), 488 deletions(-) create mode 100644 docs/rest-apis/gateway/webbroker-api-management.md delete mode 100644 gateway/gateway-controller/pkg/api/management/webbroker_types.go diff --git a/docs/rest-apis/gateway/README.md b/docs/rest-apis/gateway/README.md index 5955513ae..54f70c282 100644 --- a/docs/rest-apis/gateway/README.md +++ b/docs/rest-apis/gateway/README.md @@ -102,9 +102,21 @@ Base URLs: - [Create a new WebSubAPI](websub-api-management.md#create-a-new-websubapi) - [List all WebSubAPIs](websub-api-management.md#list-all-websubapis) +- [Create a new API key for a WebSub API](websub-api-management.md#create-a-new-api-key-for-a-websub-api) +- [Get the list of API keys for a WebSub API](websub-api-management.md#get-the-list-of-api-keys-for-a-websub-api) +- [Regenerate API key for a WebSub API](websub-api-management.md#regenerate-api-key-for-a-websub-api) +- [Update an API key for a WebSub API](websub-api-management.md#update-an-api-key-for-a-websub-api) +- [Revoke an API key for a WebSub API](websub-api-management.md#revoke-an-api-key-for-a-websub-api) - [Get WebSubAPI by id](websub-api-management.md#get-websubapi-by-id) - [Update an existing WebSubAPI](websub-api-management.md#update-an-existing-websubapi) - [Delete a WebSubAPI](websub-api-management.md#delete-a-websubapi) +### [WebBroker API Management](webbroker-api-management.md) + +- [Create a new WebBrokerAPI](webbroker-api-management.md#create-a-new-webbrokerapi) +- [List all WebBrokerAPIs](webbroker-api-management.md#list-all-webbrokerapis) +- [Get WebBrokerAPI by id](webbroker-api-management.md#get-webbrokerapi-by-id) +- [Delete a WebBrokerAPI](webbroker-api-management.md#delete-a-webbrokerapi) + ### [Schemas](schemas.md) diff --git a/docs/rest-apis/gateway/schemas.md b/docs/rest-apis/gateway/schemas.md index 973559447..91629e964 100644 --- a/docs/rest-apis/gateway/schemas.md +++ b/docs/rest-apis/gateway/schemas.md @@ -648,10 +648,154 @@ xor "main": "api.example.com", "sandbox": "sandbox-api.example.com" }, + "receiver": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "hub": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "channels": [ + { + "name": "issues", + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } + ] + }, + "delivery": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "deploymentState": "deployed" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed)| +|version|string|true|none|Semantic version of the API| +|context|string|true|none|Base path for all API routes (must start with /, no trailing slash)| +|vhosts|object|false|none|Custom virtual hosts/domains for the API| +|» main|string|true|none|Custom virtual host/domain for production traffic| +|» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| +|receiver|[WebSubReceiver](#schemawebsubreceiver)|false|none|Receiver configuration for the WebSub API - handles inbound event publishing from publishers.| +|hub|[WebSubHub](#schemawebsubhub)|true|none|Hub configuration for the WebSub API - handles subscriber management and event fan-out.| +|delivery|[WebSubDelivery](#schemawebsubdelivery)|false|none|Delivery configuration for the WebSub API - handles outbound event delivery to subscribers.| +|deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|deploymentState|deployed| +|deploymentState|undeployed| + +

WebSubReceiver

+ + + + + + +```json +{ + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] +} + +``` + +Receiver configuration for the WebSub API - handles inbound event publishing from publishers. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied to inbound webhook requests (e.g., hmac-signature-validation)| + +

WebSubDelivery

+ + + + + + +```json +{ + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] +} + +``` + +Delivery configuration for the WebSub API - handles outbound event delivery to subscribers. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied when delivering events to subscriber callback URLs (e.g., hmac-signature-validation)| + +

WebSubHub

+ + + + + + +```json +{ + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], "channels": [ { "name": "issues", - "method": "SUB", "policies": [ { "name": "cors", @@ -661,7 +805,30 @@ xor } ] } - ], + ] +} + +``` + +Hub configuration for the WebSub API - handles subscriber management and event fan-out. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied at the hub level (e.g., api-key-auth for subscribers)| +|channels|[[HubChannel](#schemahubchannel)]|true|none|List of topic channels available for subscription| + +

HubChannel

+ + + + + + +```json +{ + "name": "issues", "policies": [ { "name": "cors", @@ -669,32 +836,19 @@ xor "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "deploymentState": "deployed" + ] } ``` +A subscribable topic channel within the WebSub hub. + ### Properties |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|displayName|string|true|none|Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed)| -|version|string|true|none|Semantic version of the API| -|context|string|true|none|Base path for all API routes (must start with /, no trailing slash)| -|vhosts|object|false|none|Custom virtual hosts/domains for the API| -|» main|string|true|none|Custom virtual host/domain for production traffic| -|» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| -|channels|[[Channel](#schemachannel)]|true|none|List of channels - Async operations(SUB) for WebSub APIs| -|policies|[[Policy](#schemapolicy)]|false|none|List of API-level policies applied to all operations unless overridden| -|deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment.| - -#### Enumerated Values - -|Property|Value| -|---|---| -|deploymentState|deployed| -|deploymentState|undeployed| +|name|string|true|none|Channel name or topic identifier relative to API context.| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied only to this channel (e.g., rbac)|

Channel

@@ -735,6 +889,658 @@ Channel (topic/event stream) definition for async APIs. |---|---| |method|SUB| +

WebBrokerApiRequest

+ + + + + + +```json +{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "stock-trading-v1.0" + }, + "spec": { + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading/$version", + "receiver": { + "name": "websocket-receiver", + "type": "websocket" + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } + }, + "channels": { + "prices": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + } + } + } + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|apiVersion|string|true|none|API specification version| +|kind|string|true|none|API type| +|metadata|[Metadata](#schemametadata)|true|none|none| +|spec|[WebBrokerApiData](#schemawebbrokerapidata)|true|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|apiVersion|gateway.api-platform.wso2.com/v1alpha1| +|kind|WebBrokerApi| + +

WebBrokerApi

+ + + + + + +```json +{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "stock-trading-v1.0" + }, + "spec": { + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading/$version", + "receiver": { + "name": "websocket-receiver", + "type": "websocket" + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } + }, + "channels": { + "prices": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + } + } + } + }, + "status": { + "id": "stock-trading-v1.0", + "state": "deployed", + "createdAt": "2026-04-24T07:21:13Z", + "updatedAt": "2026-04-24T07:21:13Z", + "deployedAt": "2026-04-24T07:21:13Z" + } +} + +``` + +### Properties + +allOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[WebBrokerApiRequest](#schemawebbrokerapirequest)|false|none|none| + +and + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| +|» status|[ResourceStatus](#schemaresourcestatus)|false|read-only|Server-managed lifecycle fields. Populated on responses.| + +

WebBrokerApiData

+ + + + + + +```json +{ + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading", + "receiver": { + "name": "websocket-receiver", + "type": "websocket", + "properties": {} + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } + }, + "policies": { + "on_connection_init": { + "request": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "response": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_produce": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_consume": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "channels": { + "property1": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "response": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_produce": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_consume": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } + }, + "property2": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "response": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_produce": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_consume": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } + } + }, + "vhosts": { + "main": "api.example.com", + "sandbox": "sandbox-api.example.com" + }, + "deploymentState": "deployed" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed)| +|version|string|true|none|Semantic version of the API| +|context|string|true|none|Base path for all API routes (must start with /, no trailing slash)| +|receiver|[WebBrokerApiReceiver](#schemawebbrokerapireceiver)|true|none|WebSocket receiver configuration| +|brokerDriver|[WebBrokerApiBrokerDriver](#schemawebbrokerapibrokerdriver)|true|none|Message broker driver configuration| +|policies|[WebBrokerApiPolicies](#schemawebbrokerapipolicies)|false|none|Protocol mediation policies applied at API level| +|channels|object|true|none|Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name)| +|» **additionalProperties**|[WebBrokerApiChannel](#schemawebbrokerapichannel)|false|none|WebSocket channel configuration with Kafka topic mapping| +|vhosts|object|false|none|Custom virtual hosts/domains for the API| +|» main|string|true|none|Custom virtual host/domain for production traffic| +|» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| +|deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration and policies are preserved for potential redeployment.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|deploymentState|deployed| +|deploymentState|undeployed| + +

WebBrokerApiReceiver

+ + + + + + +```json +{ + "name": "websocket-receiver", + "type": "websocket", + "properties": {} +} + +``` + +WebSocket receiver configuration + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|true|none|Receiver name| +|type|string|true|none|Receiver type| +|properties|object|false|none|Additional receiver properties| + +

WebBrokerApiBrokerDriver

+ + + + + + +```json +{ + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } +} + +``` + +Message broker driver configuration + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|true|none|Broker driver name| +|type|string|true|none|Broker driver type| +|properties|object|true|none|Broker driver properties (e.g., bootstrap servers)| + +

WebBrokerApiPolicies

+ + + + + + +```json +{ + "on_connection_init": { + "request": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "response": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_produce": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_consume": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] +} + +``` + +Protocol mediation policies applied at API level + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|on_connection_init|[WebBrokerApiConnectionInitPolicies](#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| +|on_produce|[[Policy](#schemapolicy)]|false|none|Policies applied when client sends message to broker| +|on_consume|[[Policy](#schemapolicy)]|false|none|Policies applied when broker message delivered to client| + +

WebBrokerApiConnectionInitPolicies

+ + + + + + +```json +{ + "request": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "response": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] +} + +``` + +Connection initialization policies + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|request|[[Policy](#schemapolicy)]|false|none|Policies applied before WebSocket upgrade| +|response|[[Policy](#schemapolicy)]|false|none|Policies applied after WebSocket upgrade| + +

WebBrokerApiChannel

+ + + + + + +```json +{ + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "response": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_produce": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_consume": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } +} + +``` + +WebSocket channel configuration with Kafka topic mapping + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|produceTo|[WebBrokerApiProduceConfig](#schemawebbrokerapiproduceconfig)|false|none|Configuration for producing messages from WebSocket to Kafka| +|consumeFrom|[WebBrokerApiConsumeConfig](#schemawebbrokerapiconsumeconfig)|false|none|Configuration for consuming messages from Kafka to WebSocket| +|policies|[WebBrokerApiChannelPolicies](#schemawebbrokerapichannelpolicies)|false|none|Channel-specific policies (override API-level policies)| + +

WebBrokerApiProduceConfig

+ + + + + + +```json +{ + "topic": "stock.prices" +} + +``` + +Configuration for producing messages from WebSocket to Kafka + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|topic|string|true|none|Kafka topic to produce messages to| + +

WebBrokerApiConsumeConfig

+ + + + + + +```json +{ + "topic": "stock.prices" +} + +``` + +Configuration for consuming messages from Kafka to WebSocket + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|topic|string|true|none|Kafka topic to consume messages from| + +

WebBrokerApiChannelPolicies

+ + + + + + +```json +{ + "on_connection_init": { + "request": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "response": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_produce": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_consume": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] +} + +``` + +Channel-specific policies (override API-level policies) + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|on_connection_init|[WebBrokerApiConnectionInitPolicies](#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| +|on_produce|[[Policy](#schemapolicy)]|false|none|Policies applied when client sends message to broker on this channel| +|on_consume|[[Policy](#schemapolicy)]|false|none|Policies applied when broker message delivered to client on this channel| +

APIKeyCreationRequest

diff --git a/docs/rest-apis/gateway/webbroker-api-management.md b/docs/rest-apis/gateway/webbroker-api-management.md new file mode 100644 index 000000000..f63c97a58 --- /dev/null +++ b/docs/rest-apis/gateway/webbroker-api-management.md @@ -0,0 +1,518 @@ +

WebBroker API Management

+ +## Create a new WebBrokerAPI + + + +`POST /webbroker-apis` + +> Code samples + +```shell + +curl -X POST http://localhost:9090/api/management/v0.9/webbroker-apis \ + -u {username}:{password} \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Add a new WebBrokerAPI to the Gateway. WebBrokerAPI provides bidirectional streaming between WebSocket clients and Kafka brokers with per-connection isolation. + +> Payload + +```json +{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "stock-trading-v1.0" + }, + "spec": { + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading/$version", + "receiver": { + "name": "websocket-receiver", + "type": "websocket" + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } + }, + "channels": { + "prices": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + } + } + } + } +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[WebBrokerApiRequest](schemas.md#schemawebbrokerapirequest)|true|none| + +> Example responses + +> 201 Response + +```json +{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "stock-trading-v1.0" + }, + "spec": { + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading/$version", + "receiver": { + "name": "websocket-receiver", + "type": "websocket" + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } + }, + "channels": { + "prices": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + } + } + } + }, + "status": { + "id": "stock-trading-v1.0", + "state": "deployed", + "createdAt": "2026-04-24T07:21:13Z", + "updatedAt": "2026-04-24T07:21:13Z", + "deployedAt": "2026-04-24T07:21:13Z" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|WebBrokerAPI created successfully|[WebBrokerApi](schemas.md#schemawebbrokerapi)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Invalid configuration (validation failed)|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict - WebBroker API with same name and version already exists|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## List all WebBrokerAPIs + + + +`GET /webbroker-apis` + +> Code samples + +```shell + +curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis \ + -u {username}:{password} \ + -H 'Accept: application/json' + +``` + +List WebBrokerAPIs registered in the Gateway, optionally filtered by name, version, or status. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|displayName|query|string|false|Filter by WebBroker API display name| +|version|query|string|false|Filter by WebBroker API version| +|status|query|string|false|Filter by deployment status| + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|status|deployed| +|status|undeployed| + +> Example responses + +> 200 Response + +```json +{ + "status": "success", + "count": 3, + "apis": [ + { + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "stock-trading-v1.0" + }, + "spec": { + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading/$version", + "receiver": { + "name": "websocket-receiver", + "type": "websocket" + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } + }, + "channels": { + "prices": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + } + } + } + }, + "status": { + "id": "stock-trading-v1.0", + "state": "deployed", + "createdAt": "2026-04-24T07:21:13Z", + "updatedAt": "2026-04-24T07:21:13Z", + "deployedAt": "2026-04-24T07:21:13Z" + } + } + ] +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of WebBrokerAPIs|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +

Response Schema

+ +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» status|string|false|none|none| +|» count|integer|false|none|none| +|» apis|[allOf]|false|none|none| + +*allOf* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|[WebBrokerApiRequest](schemas.md#schemawebbrokerapirequest)|false|none|none| +|»»» apiVersion|string|true|none|API specification version| +|»»» kind|string|true|none|API type| +|»»» metadata|[Metadata](schemas.md#schemametadata)|true|none|none| +|»»»» name|string|true|none|Unique handle for the resource| +|»»»» labels|object|false|none|Labels are key-value pairs for organizing and selecting APIs. Keys must not contain spaces.| +|»»»»» **additionalProperties**|string|false|none|none| +|»»»» annotations|object|false|none|Annotations are arbitrary non-identifying metadata. Use domain-prefixed keys.| +|»»»»» **additionalProperties**|string|false|none|none| +|»»» spec|[WebBrokerApiData](schemas.md#schemawebbrokerapidata)|true|none|none| +|»»»» displayName|string|true|none|Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed)| +|»»»» version|string|true|none|Semantic version of the API| +|»»»» context|string|true|none|Base path for all API routes (must start with /, no trailing slash)| +|»»»» receiver|[WebBrokerApiReceiver](schemas.md#schemawebbrokerapireceiver)|true|none|WebSocket receiver configuration| +|»»»»» name|string|true|none|Receiver name| +|»»»»» type|string|true|none|Receiver type| +|»»»»» properties|object|false|none|Additional receiver properties| +|»»»» brokerDriver|[WebBrokerApiBrokerDriver](schemas.md#schemawebbrokerapibrokerdriver)|true|none|Message broker driver configuration| +|»»»»» name|string|true|none|Broker driver name| +|»»»»» type|string|true|none|Broker driver type| +|»»»»» properties|object|true|none|Broker driver properties (e.g., bootstrap servers)| +|»»»» policies|[WebBrokerApiPolicies](schemas.md#schemawebbrokerapipolicies)|false|none|Protocol mediation policies applied at API level| +|»»»»» on_connection_init|[WebBrokerApiConnectionInitPolicies](schemas.md#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| +|»»»»»» request|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied before WebSocket upgrade| +|»»»»»»» name|string|true|none|Name of the policy| +|»»»»»»» version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected. The Gateway Controller resolves the major version to the single matching full version installed in the gateway image.| +|»»»»»»» executionCondition|string|false|none|Expression controlling conditional execution of the policy| +|»»»»»»» params|object|false|none|Arbitrary parameters for the policy (free-form key/value structure)| +|»»»»»» response|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied after WebSocket upgrade| +|»»»»» on_produce|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when client sends message to broker| +|»»»»» on_consume|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when broker message delivered to client| +|»»»» channels|object|true|none|Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name)| +|»»»»» **additionalProperties**|[WebBrokerApiChannel](schemas.md#schemawebbrokerapichannel)|false|none|WebSocket channel configuration with Kafka topic mapping| +|»»»»»» produceTo|[WebBrokerApiProduceConfig](schemas.md#schemawebbrokerapiproduceconfig)|false|none|Configuration for producing messages from WebSocket to Kafka| +|»»»»»»» topic|string|true|none|Kafka topic to produce messages to| +|»»»»»» consumeFrom|[WebBrokerApiConsumeConfig](schemas.md#schemawebbrokerapiconsumeconfig)|false|none|Configuration for consuming messages from Kafka to WebSocket| +|»»»»»»» topic|string|true|none|Kafka topic to consume messages from| +|»»»»»» policies|[WebBrokerApiChannelPolicies](schemas.md#schemawebbrokerapichannelpolicies)|false|none|Channel-specific policies (override API-level policies)| +|»»»»»»» on_connection_init|[WebBrokerApiConnectionInitPolicies](schemas.md#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| +|»»»»»»» on_produce|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when client sends message to broker on this channel| +|»»»»»»» on_consume|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when broker message delivered to client on this channel| +|»»»» vhosts|object|false|none|Custom virtual hosts/domains for the API| +|»»»»» main|string|true|none|Custom virtual host/domain for production traffic| +|»»»»» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| +|»»»» deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration and policies are preserved for potential redeployment.| + +*and* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|object|false|none|none| +|»»» status|[ResourceStatus](schemas.md#schemaresourcestatus)|false|read-only|Server-managed lifecycle fields. Populated on responses.| +|»»»» id|string|false|none|Unique identifier assigned by the server (equal to metadata.name)| +|»»»» state|string|false|none|Desired deployment state reported by the server| +|»»»» createdAt|string(date-time)|false|none|Timestamp when the resource was first created (UTC)| +|»»»» updatedAt|string(date-time)|false|none|Timestamp when the resource was last updated (UTC)| +|»»»» deployedAt|string(date-time)|false|none|Timestamp when the resource was last deployed (omitted when undeployed)| + +#### Enumerated Values + +|Property|Value| +|---|---| +|apiVersion|gateway.api-platform.wso2.com/v1alpha1| +|kind|WebBrokerApi| +|deploymentState|deployed| +|deploymentState|undeployed| +|state|deployed| +|state|undeployed| + +## Get WebBrokerAPI by id + + + +`GET /webbroker-apis/{id}` + +> Code samples + +```shell + +curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis/{id} \ + -u {username}:{password} \ + -H 'Accept: application/json' + +``` + +Get a WebBrokerAPI by its ID. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|id|path|string|true|Unique public identifier for the WebBroker API.| + +#### Detailed descriptions + +**id**: Unique public identifier for the WebBroker API. + +> Example responses + +> 200 Response + +```json +{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "stock-trading-v1.0" + }, + "spec": { + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading/$version", + "receiver": { + "name": "websocket-receiver", + "type": "websocket" + }, + "brokerDriver": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "bootstrapServers": [ + "kafka-broker-1:9092", + "kafka-broker-2:9092" + ] + } + }, + "channels": { + "prices": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "stock.prices" + }, + "policies": { + "on_connection_init": { + "request": [], + "response": [] + }, + "on_produce": [], + "on_consume": [] + } + } + } + }, + "status": { + "id": "stock-trading-v1.0", + "state": "deployed", + "createdAt": "2026-04-24T07:21:13Z", + "updatedAt": "2026-04-24T07:21:13Z", + "deployedAt": "2026-04-24T07:21:13Z" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|WebBrokerAPI details|[WebBrokerApi](schemas.md#schemawebbrokerapi)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|WebBrokerAPI not found|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Delete a WebBrokerAPI + + + +`DELETE /webbroker-apis/{id}` + +> Code samples + +```shell + +curl -X DELETE http://localhost:9090/api/management/v0.9/webbroker-apis/{id} \ + -u {username}:{password} \ + -H 'Accept: application/json' + +``` + +Delete a WebBrokerAPI from the Gateway. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|id|path|string|true|Unique public identifier of the WebBroker API to delete.| + +#### Detailed descriptions + +**id**: Unique public identifier of the WebBroker API to delete. + +> Example responses + +> 200 Response + +```json +{ + "status": "success", + "message": "WebBrokerAPI deleted successfully", + "id": "stock-trading-webbroker-api" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|WebBrokerAPI deleted successfully|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|WebBrokerAPI not found|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +

Response Schema

+ +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» status|string|false|none|none| +|» message|string|false|none|none| +|» id|string|false|none|none| diff --git a/docs/rest-apis/gateway/websub-api-management.md b/docs/rest-apis/gateway/websub-api-management.md index fefdba9f1..7aac15351 100644 --- a/docs/rest-apis/gateway/websub-api-management.md +++ b/docs/rest-apis/gateway/websub-api-management.md @@ -229,15 +229,19 @@ Status Code **200** |»»»» vhosts|object|false|none|Custom virtual hosts/domains for the API| |»»»»» main|string|true|none|Custom virtual host/domain for production traffic| |»»»»» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| -|»»»» channels|[[Channel](schemas.md#schemachannel)]|true|none|List of channels - Async operations(SUB) for WebSub APIs| -|»»»»» name|string|true|none|Channel name or topic identifier relative to API context.| -|»»»»» method|string|true|none|Operation method type.| -|»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied only to this channel (overrides or adds to API-level policies)| +|»»»» receiver|[WebSubReceiver](schemas.md#schemawebsubreceiver)|false|none|Receiver configuration for the WebSub API - handles inbound event publishing from publishers.| +|»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied to inbound webhook requests (e.g., hmac-signature-validation)| |»»»»»» name|string|true|none|Name of the policy| |»»»»»» version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected. The Gateway Controller resolves the major version to the single matching full version installed in the gateway image.| |»»»»»» executionCondition|string|false|none|Expression controlling conditional execution of the policy| |»»»»»» params|object|false|none|Arbitrary parameters for the policy (free-form key/value structure)| -|»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of API-level policies applied to all operations unless overridden| +|»»»» hub|[WebSubHub](schemas.md#schemawebsubhub)|true|none|Hub configuration for the WebSub API - handles subscriber management and event fan-out.| +|»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied at the hub level (e.g., api-key-auth for subscribers)| +|»»»»» channels|[[HubChannel](schemas.md#schemahubchannel)]|true|none|List of topic channels available for subscription| +|»»»»»» name|string|true|none|Channel name or topic identifier relative to API context.| +|»»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied only to this channel (e.g., rbac)| +|»»»» delivery|[WebSubDelivery](schemas.md#schemawebsubdelivery)|false|none|Delivery configuration for the WebSub API - handles outbound event delivery to subscribers.| +|»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied when delivering events to subscriber callback URLs (e.g., hmac-signature-validation)| |»»»» deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment.| *and* @@ -258,12 +262,360 @@ Status Code **200** |---|---| |apiVersion|gateway.api-platform.wso2.com/v1alpha1| |kind|WebSubApi| -|method|SUB| |deploymentState|deployed| |deploymentState|undeployed| |state|deployed| |state|undeployed| +## Create a new API key for a WebSub API + + + +`POST /websub-apis/{id}/api-keys` + +> Code samples + +```shell + +curl -X POST http://localhost:9090/api/management/v0.9/websub-apis/{id}/api-keys \ + -u {username}:{password} \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Generate a new API key for a WebSub API in the Gateway. The key is a 32-byte random value encoded in hexadecimal, prefixed with `apip_`. Use the API Key policy on the API to validate incoming requests with this key. + +> Payload + +```json +{ + "name": "my-production-key" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|id|path|string|true|Unique public identifier of the WebSub API to generate the key for| +|body|body|[APIKeyCreationRequest](schemas.md#schemaapikeycreationrequest)|true|none| + +> Example responses + +> 201 Response + +```json +{ + "status": "success", + "message": "API key generated successfully", + "remainingApiKeyQuota": 9, + "apiKey": { + "name": "my-production-key", + "displayName": "My Production Key", + "apiKey": "apip_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "apiId": "reading-list-api-v1.0", + "status": "active", + "createdAt": "2026-04-01T10:30:00Z", + "createdBy": "admin", + "expiresAt": null, + "source": "local" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|API key created successfully|[APIKeyCreationResponse](schemas.md#schemaapikeycreationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Invalid configuration (validation failed)|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|WebSub API not found|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict (duplicate key or conflicting update)|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Get the list of API keys for a WebSub API + + + +`GET /websub-apis/{id}/api-keys` + +> Code samples + +```shell + +curl -X GET http://localhost:9090/api/management/v0.9/websub-apis/{id}/api-keys \ + -u {username}:{password} \ + -H 'Accept: application/json' + +``` + +List all API keys for a WebSub API in the Gateway. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|id|path|string|true|Unique public identifier of the WebSub API to retrieve the keys for| + +> Example responses + +> 200 Response + +```json +{ + "apiKeys": [ + { + "name": "my-production-key", + "displayName": "My Production Key", + "apiKey": "apip_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "apiId": "reading-list-api-v1.0", + "status": "active", + "createdAt": "2026-04-01T10:30:00Z", + "createdBy": "admin", + "expiresAt": null, + "source": "local" + } + ], + "totalCount": 3, + "status": "success" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API keys|[APIKeyListResponse](schemas.md#schemaapikeylistresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|WebSub API not found|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Regenerate API key for a WebSub API + + + +`POST /websub-apis/{id}/api-keys/{apiKeyName}/regenerate` + +> Code samples + +```shell + +curl -X POST http://localhost:9090/api/management/v0.9/websub-apis/{id}/api-keys/{apiKeyName}/regenerate \ + -u {username}:{password} \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Regenerate an existing API key for a WebSub API in the Gateway. The previous key is revoked and replaced with a new 32-byte random value encoded in hexadecimal, prefixed with `apip_`. + +> Payload + +```json +{} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|id|path|string|true|Unique public identifier of the WebSub API| +|apiKeyName|path|string|true|Name of the API key to regenerate| +|body|body|[APIKeyRegenerationRequest](schemas.md#schemaapikeyregenerationrequest)|true|none| + +> Example responses + +> 200 Response + +```json +{ + "status": "success", + "message": "API key generated successfully", + "remainingApiKeyQuota": 9, + "apiKey": { + "name": "my-production-key", + "displayName": "My Production Key", + "apiKey": "apip_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "apiId": "reading-list-api-v1.0", + "status": "active", + "createdAt": "2026-04-01T10:30:00Z", + "createdBy": "admin", + "expiresAt": null, + "source": "local" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API key rotated successfully|[APIKeyCreationResponse](schemas.md#schemaapikeycreationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Invalid configuration (validation failed)|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|WebSub API or API key not found|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Update an API key for a WebSub API + + + +`PUT /websub-apis/{id}/api-keys/{apiKeyName}` + +> Code samples + +```shell + +curl -X PUT http://localhost:9090/api/management/v0.9/websub-apis/{id}/api-keys/{apiKeyName} \ + -u {username}:{password} \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Update an API key with a custom value instead of auto-generating one. + +> Payload + +```json +{ + "name": "my-production-key" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|id|path|string|true|Unique public identifier of the WebSub API| +|apiKeyName|path|string|true|Name of the API key to update| +|body|body|[APIKeyUpdateRequest](schemas.md#schemaapikeyupdaterequest)|true|none| + +> Example responses + +> 200 Response + +```json +{ + "status": "success", + "message": "API key generated successfully", + "remainingApiKeyQuota": 9, + "apiKey": { + "name": "my-production-key", + "displayName": "My Production Key", + "apiKey": "apip_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "apiId": "reading-list-api-v1.0", + "status": "active", + "createdAt": "2026-04-01T10:30:00Z", + "createdBy": "admin", + "expiresAt": null, + "source": "local" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API key updated successfully|[APIKeyCreationResponse](schemas.md#schemaapikeycreationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Invalid request (validation failed)|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|WebSub API or API key not found|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict (duplicate key or conflicting update)|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Revoke an API key for a WebSub API + + + +`DELETE /websub-apis/{id}/api-keys/{apiKeyName}` + +> Code samples + +```shell + +curl -X DELETE http://localhost:9090/api/management/v0.9/websub-apis/{id}/api-keys/{apiKeyName} \ + -u {username}:{password} \ + -H 'Accept: application/json' + +``` + +Revoke an API key. Once revoked, it can no longer be used to authenticate requests. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|id|path|string|true|Unique public identifier of the WebSub API| +|apiKeyName|path|string|true|Name of the API key to revoke| + +> Example responses + +> 200 Response + +```json +{ + "status": "success", + "message": "API key revoked successfully" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API key revoked successfully|[APIKeyRevocationResponse](schemas.md#schemaapikeyrevocationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Invalid configuration (validation failed)|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|WebSub API or API key not found|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[ErrorResponse](schemas.md#schemaerrorresponse)| + ## Get WebSubAPI by id diff --git a/event-gateway/gateway-runtime/internal/binding/types.go b/event-gateway/gateway-runtime/internal/binding/types.go index 350a37561..d68da36ba 100644 --- a/event-gateway/gateway-runtime/internal/binding/types.go +++ b/event-gateway/gateway-runtime/internal/binding/types.go @@ -78,11 +78,18 @@ type WebBrokerApiBinding struct { // WebBrokerChannelDef defines a single channel within a WebBrokerApi with its policies. type WebBrokerChannelDef struct { + ProduceTo *TopicMapping `yaml:"produce_to,omitempty"` + ConsumeFrom *TopicMapping `yaml:"consume_from,omitempty"` OnConnectionInit ConnectionInitPolicies `yaml:"on_connection_init"` OnProduce []PolicyRef `yaml:"on_produce"` OnConsume []PolicyRef `yaml:"on_consume"` } +// TopicMapping defines a Kafka topic mapping +type TopicMapping struct { + Topic string `yaml:"topic"` +} + // ProtocolMediationPolicies defines policy enforcement points for protocol mediation. type ProtocolMediationPolicies struct { OnConnectionInit ConnectionInitPolicies `yaml:"on_connection_init"` diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go index 5667438bc..a0429a04b 100644 --- a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -67,6 +67,8 @@ type brokerApiConnection struct { channelName string // Selected channel from X-topic header produceChainKey string // Policy chain key for on_produce consumeChainKey string // Policy chain key for on_consume + produceTopic string // Target Kafka topic for producing messages + consumeTopic string // Source Kafka topic for consuming messages } // NewBrokerApiReceiver creates a WebSocket receiver for WebBrokerApi protocol mediation. @@ -256,6 +258,7 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ // Extract channel-specific policy chain keys from metadata. var produceChainKey, consumeChainKey string + var produceTopic, consumeTopic string if channelChainsIface, ok := e.channel.Metadata["channelChains"]; ok { // channelChains is stored as map[string]map[string]string if channelChainsMap, ok := channelChainsIface.(map[string]map[string]string); ok { @@ -266,13 +269,29 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ } } - // Extract topics for this channel. - topicToChannelIface, _ := e.channel.Metadata["topicToChannel"] - topicToChannel, _ := topicToChannelIface.(map[string]string) + // Extract channel topic mappings (produceTo, consumeFrom) + if channelTopicsIface, ok := e.channel.Metadata["channelTopics"]; ok { + if channelTopicsMap, ok := channelTopicsIface.(map[string]map[string]string); ok { + if topicMapping, ok := channelTopicsMap[channelName]; ok { + produceTopic = topicMapping["produceTo"] + consumeTopic = topicMapping["consumeFrom"] + } + } + } + + // Determine topics for this channel from metadata. + // Use the consumeTopic from channel config, fallback to topicToChannel mapping. channelTopics := []string{} - for topic, ch := range topicToChannel { - if ch == channelName { - channelTopics = append(channelTopics, topic) + if consumeTopic != "" { + channelTopics = append(channelTopics, consumeTopic) + } else { + // Fallback: extract topics from topicToChannel mapping (legacy) + topicToChannelIface, _ := e.channel.Metadata["topicToChannel"] + topicToChannel, _ := topicToChannelIface.(map[string]string) + for topic, ch := range topicToChannel { + if ch == channelName { + channelTopics = append(channelTopics, topic) + } } } if len(channelTopics) == 0 { @@ -292,6 +311,8 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ channelName: channelName, produceChainKey: produceChainKey, consumeChainKey: consumeChainKey, + produceTopic: produceTopic, + consumeTopic: consumeTopic, } // Create unique consumer group for this connection. @@ -413,13 +434,16 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC continue } - // Determine target topic from processed message. - // The topic should be set by policies (e.g., map-topic policy) or defaults to normalized channel name. - targetTopic := processed.Topic + // Determine target topic from channel config first, then fallback to policy-set topic. + targetTopic := conn.produceTopic + if targetTopic == "" { + // Fallback: check if policy set a topic (legacy map-topic policy) + targetTopic = processed.Topic + } if targetTopic == "" { - // Fallback to normalized channel name if no policy set a topic + // Final fallback: normalized channel name targetTopic = binding.NormalizeTopicSegment(conn.channelName) - slog.Warn("No target topic set by policies, using normalized channel name as default", + slog.Warn("No target topic set in config or by policies, using normalized channel name as default", "connID", conn.connID, "channel", conn.channelName, "topic", targetTopic) diff --git a/event-gateway/gateway-runtime/internal/runtime/runtime.go b/event-gateway/gateway-runtime/internal/runtime/runtime.go index 82b46533a..b057a5044 100644 --- a/event-gateway/gateway-runtime/internal/runtime/runtime.go +++ b/event-gateway/gateway-runtime/internal/runtime/runtime.go @@ -807,12 +807,17 @@ func (r *Runtime) buildWebBrokerApiPolicyChains(wbb binding.WebBrokerApiBinding, func extractTopicsFromChannelPolicies(channelName string, channelDef binding.WebBrokerChannelDef) []string { topics := make(map[string]bool) // Use map to deduplicate - // ONLY check onConsume policies - these are the topics we subscribe to - for _, policy := range channelDef.OnConsume { - if policy.Name == "map-topic" && policy.Params != nil { - if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { - if topic, ok := policy.Params["topic"].(string); ok && topic != "" { - topics[topic] = true + // Check consumeFrom field first (new approach) + if channelDef.ConsumeFrom != nil && channelDef.ConsumeFrom.Topic != "" { + topics[channelDef.ConsumeFrom.Topic] = true + } else { + // Fallback: check onConsume policies for map-topic (legacy) + for _, policy := range channelDef.OnConsume { + if policy.Name == "map-topic" && policy.Params != nil { + if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { + if topic, ok := policy.Params["topic"].(string); ok && topic != "" { + topics[topic] = true + } } } } @@ -839,33 +844,48 @@ func extractAllTopicsFromChannelPolicies(channelName string, channelDef binding. hasProduceTopics := false hasConsumeTopics := false - // Check onProduce policies for produceTo topics - for _, policy := range channelDef.OnProduce { - if policy.Name == "map-topic" && policy.Params != nil { - if mode, ok := policy.Params["mode"].(string); ok && mode == "produceTo" { - if topic, ok := policy.Params["topic"].(string); ok && topic != "" { - topics[topic] = true - hasProduceTopics = true + // Check produceTo field first (new approach) + if channelDef.ProduceTo != nil && channelDef.ProduceTo.Topic != "" { + topics[channelDef.ProduceTo.Topic] = true + hasProduceTopics = true + } else { + // Fallback: check onProduce policies for map-topic (legacy) + for _, policy := range channelDef.OnProduce { + if policy.Name == "map-topic" && policy.Params != nil { + if mode, ok := policy.Params["mode"].(string); ok && mode == "produceTo" { + if topic, ok := policy.Params["topic"].(string); ok && topic != "" { + topics[topic] = true + hasProduceTopics = true + } } } } } - // Check onConsume policies for consumeFrom topics - for _, policy := range channelDef.OnConsume { - if policy.Name == "map-topic" && policy.Params != nil { - if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { - if topic, ok := policy.Params["topic"].(string); ok && topic != "" { - topics[topic] = true - hasConsumeTopics = true + // Check consumeFrom field first (new approach) + if channelDef.ConsumeFrom != nil && channelDef.ConsumeFrom.Topic != "" { + topics[channelDef.ConsumeFrom.Topic] = true + hasConsumeTopics = true + } else { + // Fallback: check onConsume policies for map-topic (legacy) + for _, policy := range channelDef.OnConsume { + if policy.Name == "map-topic" && policy.Params != nil { + if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { + if topic, ok := policy.Params["topic"].(string); ok && topic != "" { + topics[topic] = true + hasConsumeTopics = true + } } } } + // If still no consume topics found, use normalized channel name as default + if !hasConsumeTopics { + topics[binding.NormalizeTopicSegment(channelName)] = true + } } - // If no topics found from policies, default to normalized channel name - // This enables the channel name to be used directly as the Kafka topic - if !hasProduceTopics && !hasConsumeTopics { + // If no produce topics were found, also add normalized channel name for producing + if !hasProduceTopics { topics[binding.NormalizeTopicSegment(channelName)] = true } @@ -1132,8 +1152,9 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error // Build per-channel policy chains and collect topics. channelChains := make(map[string]ChannelPolicyChains) - allTopics := []string{} // All topics (produce + consume) for ensuring they exist - topicToChannel := make(map[string]string) // Only consume topics for subscription mapping + allTopics := []string{} // All topics (produce + consume) for ensuring they exist + topicToChannel := make(map[string]string) // Only consume topics for subscription mapping + channelTopics := make(map[string]map[string]string) // Channel-level topic mappings (produceTo, consumeFrom) for channelName, channelDef := range wbb.Channels { connInitReqKey, connInitRespKey, produceKey, consumeKey, err := r.buildWebBrokerApiPolicyChains(wbb, vhost, channelName) @@ -1149,6 +1170,22 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error ConsumeKey: consumeKey, } + // Store channel topic mappings (use defaults if not specified) + topicMapping := make(map[string]string) + if channelDef.ProduceTo != nil && channelDef.ProduceTo.Topic != "" { + topicMapping["produceTo"] = channelDef.ProduceTo.Topic + } else { + // Default: use normalized channel name for producing + topicMapping["produceTo"] = binding.NormalizeTopicSegment(channelName) + } + if channelDef.ConsumeFrom != nil && channelDef.ConsumeFrom.Topic != "" { + topicMapping["consumeFrom"] = channelDef.ConsumeFrom.Topic + } else { + // Default: use normalized channel name for consuming + topicMapping["consumeFrom"] = binding.NormalizeTopicSegment(channelName) + } + channelTopics[channelName] = topicMapping + // Extract ALL topics (produce + consume) to ensure they exist in Kafka allChannelTopics := extractAllTopicsFromChannelPolicies(channelName, channelDef) allTopics = append(allTopics, allChannelTopics...) @@ -1200,6 +1237,7 @@ func (r *Runtime) AddWebBrokerApiBinding(wbb binding.WebBrokerApiBinding) error "channelChains": channelChainsToMap(channelChains), "topicToChannel": topicToChannel, "channelNames": getChannelNames(wbb.Channels), + "channelTopics": channelTopics, }, } diff --git a/event-gateway/gateway-runtime/internal/xdsclient/handler.go b/event-gateway/gateway-runtime/internal/xdsclient/handler.go index 4cc8e7344..cae04d703 100644 --- a/event-gateway/gateway-runtime/internal/xdsclient/handler.go +++ b/event-gateway/gateway-runtime/internal/xdsclient/handler.go @@ -63,9 +63,14 @@ type EventChannelResource struct { // WebBrokerChannelEntry represents a WebBrokerApi channel with policies type WebBrokerChannelEntry struct { - OnConnectionInit ConnectionInitPolicies `json:"on_connection_init"` - OnProduce []PolicyEntry `json:"on_produce"` - OnConsume []PolicyEntry `json:"on_consume"` + ProduceTo *TopicMapping `json:"produce_to,omitempty"` + ConsumeFrom *TopicMapping `json:"consume_from,omitempty"` + Policies ProtocolMediationPolicies `json:"policies"` +} + +// TopicMapping defines a Kafka topic mapping +type TopicMapping struct { + Topic string `json:"topic"` } // ProtocolMediationPolicies defines policies for WebBrokerApi @@ -419,22 +424,39 @@ func (h *Handler) toWebBrokerApiBinding(ecr EventChannelResource) binding.WebBro if channelData, ok := channelIface.(map[string]interface{}); ok { var channelDef binding.WebBrokerChannelDef - // Parse channel on_connection_init - if connInitIface, ok := channelData["on_connection_init"].(map[string]interface{}); ok { - if reqIface, ok := connInitIface["request"].([]interface{}); ok { - channelDef.OnConnectionInit.Request = mapGenericPolicyList(reqIface) - } - if respIface, ok := connInitIface["response"].([]interface{}); ok { - channelDef.OnConnectionInit.Response = mapGenericPolicyList(respIface) + // Parse produce_to + if produceToIface, ok := channelData["produce_to"].(map[string]interface{}); ok { + if topic, ok := produceToIface["topic"].(string); ok && topic != "" { + channelDef.ProduceTo = &binding.TopicMapping{Topic: topic} } } - // Parse channel on_produce - if produceIface, ok := channelData["on_produce"].([]interface{}); ok { - channelDef.OnProduce = mapGenericPolicyList(produceIface) + + // Parse consume_from + if consumeFromIface, ok := channelData["consume_from"].(map[string]interface{}); ok { + if topic, ok := consumeFromIface["topic"].(string); ok && topic != "" { + channelDef.ConsumeFrom = &binding.TopicMapping{Topic: topic} + } } - // Parse channel on_consume - if consumeIface, ok := channelData["on_consume"].([]interface{}); ok { - channelDef.OnConsume = mapGenericPolicyList(consumeIface) + + // Parse policies from nested "policies" field + if policiesIface, ok := channelData["policies"].(map[string]interface{}); ok { + // Parse channel on_connection_init + if connInitIface, ok := policiesIface["on_connection_init"].(map[string]interface{}); ok { + if reqIface, ok := connInitIface["request"].([]interface{}); ok { + channelDef.OnConnectionInit.Request = mapGenericPolicyList(reqIface) + } + if respIface, ok := connInitIface["response"].([]interface{}); ok { + channelDef.OnConnectionInit.Response = mapGenericPolicyList(respIface) + } + } + // Parse channel on_produce + if produceIface, ok := policiesIface["on_produce"].([]interface{}); ok { + channelDef.OnProduce = mapGenericPolicyList(produceIface) + } + // Parse channel on_consume + if consumeIface, ok := policiesIface["on_consume"].([]interface{}); ok { + channelDef.OnConsume = mapGenericPolicyList(consumeIface) + } } channels[channelName] = channelDef diff --git a/gateway/gateway-controller/api/management-openapi.yaml b/gateway/gateway-controller/api/management-openapi.yaml index 7e10baf65..9bf8022b1 100644 --- a/gateway/gateway-controller/api/management-openapi.yaml +++ b/gateway/gateway-controller/api/management-openapi.yaml @@ -1354,6 +1354,190 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" + /webbroker-apis: + post: + summary: Create a new WebBrokerAPI + description: Add a new WebBrokerAPI to the Gateway. WebBrokerAPI provides bidirectional streaming between WebSocket clients and Kafka brokers with per-connection isolation. + operationId: createWebBrokerApi + x-basicauth-roles: [admin, developer] + tags: + - WebBroker API Management + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/WebBrokerApiRequest" + application/json: + schema: + $ref: "#/components/schemas/WebBrokerApiRequest" + responses: + "201": + description: WebBrokerAPI created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/WebBrokerApi" + "400": + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "409": + description: Conflict - WebBroker API with same name and version already exists + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + get: + summary: List all WebBrokerAPIs + description: List WebBrokerAPIs registered in the Gateway, optionally filtered by name, version, or status. + operationId: listWebBrokerApis + x-basicauth-roles: [admin, developer] + tags: + - WebBroker API Management + parameters: + - name: displayName + in: query + required: false + description: Filter by WebBroker API display name + schema: + type: string + example: Stock Trading WebBroker API + - name: version + in: query + required: false + description: Filter by WebBroker API version + schema: + type: string + example: v1.0 + - name: status + in: query + required: false + description: Filter by deployment status + schema: + type: string + enum: [ deployed, undeployed ] + example: deployed + responses: + "200": + description: List of WebBrokerAPIs + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: success + count: + type: integer + example: 3 + apis: + type: array + items: + $ref: "#/components/schemas/WebBrokerApi" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /webbroker-apis/{id}: + get: + summary: Get WebBrokerAPI by id + description: Get a WebBrokerAPI by its ID. + operationId: getWebBrokerApiById + x-basicauth-roles: [admin, developer] + tags: + - WebBroker API Management + parameters: + - name: id + in: path + required: true + description: | + Unique public identifier for the WebBroker API. + schema: + type: string + example: stock-trading-webbroker-api + responses: + "200": + description: WebBrokerAPI details + content: + application/json: + schema: + $ref: "#/components/schemas/WebBrokerApi" + application/yaml: + schema: + $ref: "#/components/schemas/WebBrokerApi" + "404": + description: WebBrokerAPI not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + delete: + summary: Delete a WebBrokerAPI + description: Delete a WebBrokerAPI from the Gateway. + operationId: deleteWebBrokerApiById + x-basicauth-roles: [admin, developer] + tags: + - WebBroker API Management + parameters: + - name: id + in: path + required: true + description: | + Unique public identifier of the WebBroker API to delete. + schema: + type: string + example: stock-trading-webbroker-api + responses: + "200": + description: WebBrokerAPI deleted successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: success + message: + type: string + example: WebBrokerAPI deleted successfully + id: + type: string + example: stock-trading-webbroker-api + "404": + description: WebBrokerAPI not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + /certificates: get: summary: List all custom certificates @@ -3902,6 +4086,301 @@ components: # description: Protocol-specific channel bindings (arbitrary key/value structure). # additionalProperties: true + WebBrokerApiRequest: + type: object + required: + - apiVersion + - metadata + - kind + - spec + properties: + apiVersion: + type: string + description: API specification version + example: gateway.api-platform.wso2.com/v1alpha1 + enum: + - gateway.api-platform.wso2.com/v1alpha1 + kind: + type: string + description: API type + example: WebBrokerApi + enum: + - WebBrokerApi + metadata: + $ref: "#/components/schemas/Metadata" + spec: + $ref: '#/components/schemas/WebBrokerApiData' + example: + apiVersion: gateway.api-platform.wso2.com/v1alpha1 + kind: WebBrokerApi + metadata: + name: stock-trading-v1.0 + spec: + displayName: Stock Trading WebBroker API + version: v1.0 + context: /stock-trading/$version + receiver: + name: websocket-receiver + type: websocket + brokerDriver: + name: kafka-driver + type: kafka + properties: + bootstrapServers: + - kafka-broker-1:9092 + - kafka-broker-2:9092 + channels: + prices: + produceTo: + topic: stock.prices + consumeFrom: + topic: stock.prices + policies: + on_connection_init: + request: [] + response: [] + on_produce: [] + on_consume: [] + + WebBrokerApi: + allOf: + - $ref: '#/components/schemas/WebBrokerApiRequest' + - type: object + properties: + status: + readOnly: true + description: Server-managed lifecycle fields. Populated on responses. + allOf: + - $ref: '#/components/schemas/ResourceStatus' + example: + apiVersion: gateway.api-platform.wso2.com/v1alpha1 + kind: WebBrokerApi + metadata: + name: stock-trading-v1.0 + spec: + displayName: Stock Trading WebBroker API + version: v1.0 + context: /stock-trading/$version + receiver: + name: websocket-receiver + type: websocket + brokerDriver: + name: kafka-driver + type: kafka + properties: + bootstrapServers: + - kafka-broker-1:9092 + - kafka-broker-2:9092 + channels: + prices: + produceTo: + topic: stock.prices + consumeFrom: + topic: stock.prices + policies: + on_connection_init: + request: [] + response: [] + on_produce: [] + on_consume: [] + status: + id: stock-trading-v1.0 + state: deployed + createdAt: 2026-04-24T07:21:13Z + updatedAt: 2026-04-24T07:21:13Z + deployedAt: 2026-04-24T07:21:13Z + + WebBrokerApiData: + type: object + required: + - displayName + - version + - context + - receiver + - brokerDriver + - channels + properties: + displayName: + type: string + description: Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed) + minLength: 1 + maxLength: 100 + pattern: '^[a-zA-Z0-9\-_\. ]+$' + example: Stock Trading WebBroker API + version: + type: string + description: Semantic version of the API + pattern: '^v\d+\.\d+$' + example: v1.0 + context: + type: string + description: Base path for all API routes (must start with /, no trailing slash) + pattern: '^\/[a-zA-Z0-9_\-\/]*[^\/]$' + minLength: 1 + maxLength: 200 + example: /stock-trading + receiver: + $ref: '#/components/schemas/WebBrokerApiReceiver' + brokerDriver: + $ref: '#/components/schemas/WebBrokerApiBrokerDriver' + policies: + $ref: '#/components/schemas/WebBrokerApiPolicies' + channels: + type: object + description: Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name) + minProperties: 1 + additionalProperties: + $ref: '#/components/schemas/WebBrokerApiChannel' + vhosts: + type: object + required: + - main + description: Custom virtual hosts/domains for the API + properties: + main: + type: string + description: Custom virtual host/domain for production traffic + pattern: '^[a-zA-Z0-9\.\-]+$' + example: api.example.com + sandbox: + type: string + description: Custom virtual host/domain for sandbox traffic + pattern: '^[a-zA-Z0-9\.\-]+$' + example: sandbox-api.example.com + deploymentState: + type: string + description: Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration and policies are preserved for potential redeployment. + enum: [deployed, undeployed] + default: deployed + example: deployed + + WebBrokerApiReceiver: + type: object + description: WebSocket receiver configuration + required: + - name + - type + properties: + name: + type: string + description: Receiver name + example: websocket-receiver + type: + type: string + description: Receiver type + example: websocket + properties: + type: object + description: Additional receiver properties + additionalProperties: true + + WebBrokerApiBrokerDriver: + type: object + description: Message broker driver configuration + required: + - name + - type + - properties + properties: + name: + type: string + description: Broker driver name + example: kafka-driver + type: + type: string + description: Broker driver type + example: kafka + properties: + type: object + description: Broker driver properties (e.g., bootstrap servers) + additionalProperties: true + example: + bootstrapServers: + - kafka-broker-1:9092 + - kafka-broker-2:9092 + + WebBrokerApiPolicies: + type: object + description: Protocol mediation policies applied at API level + properties: + on_connection_init: + $ref: '#/components/schemas/WebBrokerApiConnectionInitPolicies' + on_produce: + type: array + description: Policies applied when client sends message to broker + items: + $ref: '#/components/schemas/Policy' + on_consume: + type: array + description: Policies applied when broker message delivered to client + items: + $ref: '#/components/schemas/Policy' + + WebBrokerApiConnectionInitPolicies: + type: object + description: Connection initialization policies + properties: + request: + type: array + description: Policies applied before WebSocket upgrade + items: + $ref: '#/components/schemas/Policy' + response: + type: array + description: Policies applied after WebSocket upgrade + items: + $ref: '#/components/schemas/Policy' + + WebBrokerApiChannel: + type: object + description: WebSocket channel configuration with Kafka topic mapping + properties: + produceTo: + $ref: '#/components/schemas/WebBrokerApiProduceConfig' + consumeFrom: + $ref: '#/components/schemas/WebBrokerApiConsumeConfig' + policies: + $ref: '#/components/schemas/WebBrokerApiChannelPolicies' + + WebBrokerApiProduceConfig: + type: object + description: Configuration for producing messages from WebSocket to Kafka + required: + - topic + properties: + topic: + type: string + description: Kafka topic to produce messages to + example: stock.prices + + WebBrokerApiConsumeConfig: + type: object + description: Configuration for consuming messages from Kafka to WebSocket + required: + - topic + properties: + topic: + type: string + description: Kafka topic to consume messages from + example: stock.prices + + WebBrokerApiChannelPolicies: + type: object + description: Channel-specific policies (override API-level policies) + properties: + on_connection_init: + $ref: '#/components/schemas/WebBrokerApiConnectionInitPolicies' + on_produce: + type: array + description: Policies applied when client sends message to broker on this channel + items: + $ref: '#/components/schemas/Policy' + on_consume: + type: array + description: Policies applied when broker message delivered to client on this channel + items: + $ref: '#/components/schemas/Policy' + APIKeyCreationRequest: type: object properties: diff --git a/gateway/gateway-controller/cmd/controller/main.go b/gateway/gateway-controller/cmd/controller/main.go index 6b418415a..67265f890 100644 --- a/gateway/gateway-controller/cmd/controller/main.go +++ b/gateway/gateway-controller/cmd/controller/main.go @@ -596,17 +596,6 @@ func main() { BaseURL: managementAPIBasePath, }) - // Register WebBrokerApi routes (custom endpoints not yet in OpenAPI spec) - managementGroup := router.Group(managementAPIBasePath) - managementGroup.POST("/webbroker-apis", apiServer.CreateWebBrokerApi) - managementGroup.GET("/webbroker-apis", apiServer.ListWebBrokerApis) - managementGroup.GET("/webbroker-apis/:id", func(c *gin.Context) { - apiServer.GetWebBrokerApiById(c, c.Param("id")) - }) - managementGroup.DELETE("/webbroker-apis/:id", func(c *gin.Context) { - apiServer.DeleteWebBrokerApiById(c, c.Param("id")) - }) - // Also register the same routes on the legacy unprefixed paths for // backwards compatibility. These are deprecated; responses include // RFC 8594 `Deprecation: true` and a `Link` header pointing to the new diff --git a/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go b/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go index b0e0e29db..1a25a3ae4 100644 --- a/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go +++ b/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go @@ -90,7 +90,7 @@ func (s *APIServer) CreateWebBrokerApi(c *gin.Context) { } // ListWebBrokerApis handles GET /webbroker-apis -func (s *APIServer) ListWebBrokerApis(c *gin.Context) { +func (s *APIServer) ListWebBrokerApis(c *gin.Context, params api.ListWebBrokerApisParams) { configs, err := s.db.GetAllConfigsByKind(string(models.KindWebBrokerApi)) if err != nil { s.logger.Error("Failed to list WebBrokerApis", slog.Any("error", err)) @@ -101,6 +101,8 @@ func (s *APIServer) ListWebBrokerApis(c *gin.Context) { return } + // TODO: Implement query parameter filtering (displayName, version, status) + // For now, returning all WebBrokerApis without filtering items := make([]any, 0, len(configs)) for _, cfg := range configs { items = append(items, buildResourceResponseFromStored(cfg.SourceConfiguration, cfg)) diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index 53aa1776a..1eb380bb7 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -372,6 +372,32 @@ const ( UpstreamAuthAuthTypeApiKey UpstreamAuthAuthType = "api-key" ) +// Defines values for WebBrokerApiApiVersion. +const ( + WebBrokerApiApiVersionGatewayApiPlatformWso2Comv1alpha1 WebBrokerApiApiVersion = "gateway.api-platform.wso2.com/v1alpha1" +) + +// Defines values for WebBrokerApiKind. +const ( + WebBrokerApiKindWebBrokerApi WebBrokerApiKind = "WebBrokerApi" +) + +// Defines values for WebBrokerApiDataDeploymentState. +const ( + WebBrokerApiDataDeploymentStateDeployed WebBrokerApiDataDeploymentState = "deployed" + WebBrokerApiDataDeploymentStateUndeployed WebBrokerApiDataDeploymentState = "undeployed" +) + +// Defines values for WebBrokerApiRequestApiVersion. +const ( + WebBrokerApiRequestApiVersionGatewayApiPlatformWso2Comv1alpha1 WebBrokerApiRequestApiVersion = "gateway.api-platform.wso2.com/v1alpha1" +) + +// Defines values for WebBrokerApiRequestKind. +const ( + WebBrokerApiRequestKindWebBrokerApi WebBrokerApiRequestKind = "WebBrokerApi" +) + // Defines values for WebSubAPIApiVersion. const ( WebSubAPIApiVersionGatewayApiPlatformWso2Comv1alpha1 WebSubAPIApiVersion = "gateway.api-platform.wso2.com/v1alpha1" @@ -429,6 +455,12 @@ const ( REVOKED ListSubscriptionsParamsStatus = "REVOKED" ) +// Defines values for ListWebBrokerApisParamsStatus. +const ( + ListWebBrokerApisParamsStatusDeployed ListWebBrokerApisParamsStatus = "deployed" + ListWebBrokerApisParamsStatusUndeployed ListWebBrokerApisParamsStatus = "undeployed" +) + // Defines values for ListWebSubAPIsParamsStatus. const ( Deployed ListWebSubAPIsParamsStatus = "deployed" @@ -1554,6 +1586,163 @@ type ValidationError struct { Message *string `json:"message,omitempty" yaml:"message,omitempty"` } +// WebBrokerApi defines model for WebBrokerApi. +type WebBrokerApi struct { + // ApiVersion API specification version + ApiVersion WebBrokerApiApiVersion `json:"apiVersion" yaml:"apiVersion"` + + // Kind API type + Kind WebBrokerApiKind `json:"kind" yaml:"kind"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec WebBrokerApiData `json:"spec" yaml:"spec"` + + // Status Server-managed lifecycle fields. Populated on responses. + Status *ResourceStatus `json:"status,omitempty" yaml:"status,omitempty"` +} + +// WebBrokerApiApiVersion API specification version +type WebBrokerApiApiVersion string + +// WebBrokerApiKind API type +type WebBrokerApiKind string + +// WebBrokerApiBrokerDriver Message broker driver configuration +type WebBrokerApiBrokerDriver struct { + // Name Broker driver name + Name string `json:"name" yaml:"name"` + + // Properties Broker driver properties (e.g., bootstrap servers) + Properties map[string]interface{} `json:"properties" yaml:"properties"` + + // Type Broker driver type + Type string `json:"type" yaml:"type"` +} + +// WebBrokerApiChannel WebSocket channel configuration with Kafka topic mapping +type WebBrokerApiChannel struct { + // ConsumeFrom Configuration for consuming messages from Kafka to WebSocket + ConsumeFrom *WebBrokerApiConsumeConfig `json:"consumeFrom,omitempty" yaml:"consumeFrom,omitempty"` + + // Policies Channel-specific policies (override API-level policies) + Policies *WebBrokerApiChannelPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` + + // ProduceTo Configuration for producing messages from WebSocket to Kafka + ProduceTo *WebBrokerApiProduceConfig `json:"produceTo,omitempty" yaml:"produceTo,omitempty"` +} + +// WebBrokerApiChannelPolicies Channel-specific policies (override API-level policies) +type WebBrokerApiChannelPolicies struct { + // OnConnectionInit Connection initialization policies + OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` + + // OnConsume Policies applied when broker message delivered to client on this channel + OnConsume *[]Policy `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` + + // OnProduce Policies applied when client sends message to broker on this channel + OnProduce *[]Policy `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` +} + +// WebBrokerApiConnectionInitPolicies Connection initialization policies +type WebBrokerApiConnectionInitPolicies struct { + // Request Policies applied before WebSocket upgrade + Request *[]Policy `json:"request,omitempty" yaml:"request,omitempty"` + + // Response Policies applied after WebSocket upgrade + Response *[]Policy `json:"response,omitempty" yaml:"response,omitempty"` +} + +// WebBrokerApiConsumeConfig Configuration for consuming messages from Kafka to WebSocket +type WebBrokerApiConsumeConfig struct { + // Topic Kafka topic to consume messages from + Topic string `json:"topic" yaml:"topic"` +} + +// WebBrokerApiData defines model for WebBrokerApiData. +type WebBrokerApiData struct { + // BrokerDriver Message broker driver configuration + BrokerDriver WebBrokerApiBrokerDriver `json:"brokerDriver" yaml:"brokerDriver"` + + // Channels Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name) + Channels map[string]WebBrokerApiChannel `json:"channels" yaml:"channels"` + + // Context Base path for all API routes (must start with /, no trailing slash) + Context string `json:"context" yaml:"context"` + + // DeploymentState Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration and policies are preserved for potential redeployment. + DeploymentState *WebBrokerApiDataDeploymentState `json:"deploymentState,omitempty" yaml:"deploymentState,omitempty"` + + // DisplayName Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed) + DisplayName string `json:"displayName" yaml:"displayName"` + + // Policies Protocol mediation policies applied at API level + Policies *WebBrokerApiPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` + + // Receiver WebSocket receiver configuration + Receiver WebBrokerApiReceiver `json:"receiver" yaml:"receiver"` + + // Version Semantic version of the API + Version string `json:"version" yaml:"version"` + + // Vhosts Custom virtual hosts/domains for the API + Vhosts *struct { + // Main Custom virtual host/domain for production traffic + Main string `json:"main" yaml:"main"` + + // Sandbox Custom virtual host/domain for sandbox traffic + Sandbox *string `json:"sandbox,omitempty" yaml:"sandbox,omitempty"` + } `json:"vhosts,omitempty" yaml:"vhosts,omitempty"` +} + +// WebBrokerApiDataDeploymentState Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration and policies are preserved for potential redeployment. +type WebBrokerApiDataDeploymentState string + +// WebBrokerApiPolicies Protocol mediation policies applied at API level +type WebBrokerApiPolicies struct { + // OnConnectionInit Connection initialization policies + OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` + + // OnConsume Policies applied when broker message delivered to client + OnConsume *[]Policy `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` + + // OnProduce Policies applied when client sends message to broker + OnProduce *[]Policy `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` +} + +// WebBrokerApiProduceConfig Configuration for producing messages from WebSocket to Kafka +type WebBrokerApiProduceConfig struct { + // Topic Kafka topic to produce messages to + Topic string `json:"topic" yaml:"topic"` +} + +// WebBrokerApiReceiver WebSocket receiver configuration +type WebBrokerApiReceiver struct { + // Name Receiver name + Name string `json:"name" yaml:"name"` + + // Properties Additional receiver properties + Properties *map[string]interface{} `json:"properties,omitempty" yaml:"properties,omitempty"` + + // Type Receiver type + Type string `json:"type" yaml:"type"` +} + +// WebBrokerApiRequest defines model for WebBrokerApiRequest. +type WebBrokerApiRequest struct { + // ApiVersion API specification version + ApiVersion WebBrokerApiRequestApiVersion `json:"apiVersion" yaml:"apiVersion"` + + // Kind API type + Kind WebBrokerApiRequestKind `json:"kind" yaml:"kind"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec WebBrokerApiData `json:"spec" yaml:"spec"` +} + +// WebBrokerApiRequestApiVersion API specification version +type WebBrokerApiRequestApiVersion string + +// WebBrokerApiRequestKind API type +type WebBrokerApiRequestKind string + // WebSubAPI defines model for WebSubAPI. type WebSubAPI struct { // ApiVersion API specification version @@ -1743,6 +1932,21 @@ type ListSubscriptionsParams struct { // ListSubscriptionsParamsStatus defines parameters for ListSubscriptions. type ListSubscriptionsParamsStatus string +// ListWebBrokerApisParams defines parameters for ListWebBrokerApis. +type ListWebBrokerApisParams struct { + // DisplayName Filter by WebBroker API display name + DisplayName *string `form:"displayName,omitempty" json:"displayName,omitempty" yaml:"displayName,omitempty"` + + // Version Filter by WebBroker API version + Version *string `form:"version,omitempty" json:"version,omitempty" yaml:"version,omitempty"` + + // Status Filter by deployment status + Status *ListWebBrokerApisParamsStatus `form:"status,omitempty" json:"status,omitempty" yaml:"status,omitempty"` +} + +// ListWebBrokerApisParamsStatus defines parameters for ListWebBrokerApis. +type ListWebBrokerApisParamsStatus string + // ListWebSubAPIsParams defines parameters for ListWebSubAPIs. type ListWebSubAPIsParams struct { // DisplayName Filter by WebSub API display name @@ -1839,6 +2043,9 @@ type CreateSubscriptionJSONRequestBody = SubscriptionCreateRequest // UpdateSubscriptionJSONRequestBody defines body for UpdateSubscription for application/json ContentType. type UpdateSubscriptionJSONRequestBody = SubscriptionUpdateRequest +// CreateWebBrokerApiJSONRequestBody defines body for CreateWebBrokerApi for application/json ContentType. +type CreateWebBrokerApiJSONRequestBody = WebBrokerApiRequest + // CreateWebSubAPIJSONRequestBody defines body for CreateWebSubAPI for application/json ContentType. type CreateWebSubAPIJSONRequestBody = WebSubAPIRequest @@ -2433,6 +2640,18 @@ type ServerInterface interface { // Update a subscription // (PUT /subscriptions/{subscriptionId}) UpdateSubscription(c *gin.Context, subscriptionId string) + // List all WebBrokerAPIs + // (GET /webbroker-apis) + ListWebBrokerApis(c *gin.Context, params ListWebBrokerApisParams) + // Create a new WebBrokerAPI + // (POST /webbroker-apis) + CreateWebBrokerApi(c *gin.Context) + // Delete a WebBrokerAPI + // (DELETE /webbroker-apis/{id}) + DeleteWebBrokerApiById(c *gin.Context, id string) + // Get WebBrokerAPI by id + // (GET /webbroker-apis/{id}) + GetWebBrokerApiById(c *gin.Context, id string) // List all WebSubAPIs // (GET /websub-apis) ListWebSubAPIs(c *gin.Context, params ListWebSubAPIsParams) @@ -4086,6 +4305,117 @@ func (siw *ServerInterfaceWrapper) UpdateSubscription(c *gin.Context) { siw.Handler.UpdateSubscription(c, subscriptionId) } +// ListWebBrokerApis operation middleware +func (siw *ServerInterfaceWrapper) ListWebBrokerApis(c *gin.Context) { + + var err error + + c.Set(BasicAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params ListWebBrokerApisParams + + // ------------- Optional query parameter "displayName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "displayName", c.Request.URL.Query(), ¶ms.DisplayName) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter displayName: %w", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "version" ------------- + + err = runtime.BindQueryParameter("form", true, false, "version", c.Request.URL.Query(), ¶ms.Version) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter version: %w", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "status" ------------- + + err = runtime.BindQueryParameter("form", true, false, "status", c.Request.URL.Query(), ¶ms.Status) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter status: %w", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.ListWebBrokerApis(c, params) +} + +// CreateWebBrokerApi operation middleware +func (siw *ServerInterfaceWrapper) CreateWebBrokerApi(c *gin.Context) { + + c.Set(BasicAuthScopes, []string{}) + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.CreateWebBrokerApi(c) +} + +// DeleteWebBrokerApiById operation middleware +func (siw *ServerInterfaceWrapper) DeleteWebBrokerApiById(c *gin.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) + return + } + + c.Set(BasicAuthScopes, []string{}) + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.DeleteWebBrokerApiById(c, id) +} + +// GetWebBrokerApiById operation middleware +func (siw *ServerInterfaceWrapper) GetWebBrokerApiById(c *gin.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) + return + } + + c.Set(BasicAuthScopes, []string{}) + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.GetWebBrokerApiById(c, id) +} + // ListWebSubAPIs operation middleware func (siw *ServerInterfaceWrapper) ListWebSubAPIs(c *gin.Context) { @@ -4474,6 +4804,10 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options router.DELETE(options.BaseURL+"/subscriptions/:subscriptionId", wrapper.DeleteSubscription) router.GET(options.BaseURL+"/subscriptions/:subscriptionId", wrapper.GetSubscription) router.PUT(options.BaseURL+"/subscriptions/:subscriptionId", wrapper.UpdateSubscription) + router.GET(options.BaseURL+"/webbroker-apis", wrapper.ListWebBrokerApis) + router.POST(options.BaseURL+"/webbroker-apis", wrapper.CreateWebBrokerApi) + router.DELETE(options.BaseURL+"/webbroker-apis/:id", wrapper.DeleteWebBrokerApiById) + router.GET(options.BaseURL+"/webbroker-apis/:id", wrapper.GetWebBrokerApiById) router.GET(options.BaseURL+"/websub-apis", wrapper.ListWebSubAPIs) router.POST(options.BaseURL+"/websub-apis", wrapper.CreateWebSubAPI) router.DELETE(options.BaseURL+"/websub-apis/:id", wrapper.DeleteWebSubAPI) @@ -4489,244 +4823,262 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "K9mWoWTi0+3T/wsAAP//P2OZHWT2AQA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/gateway/gateway-controller/pkg/api/management/webbroker_types.go b/gateway/gateway-controller/pkg/api/management/webbroker_types.go deleted file mode 100644 index 6c0387e78..000000000 --- a/gateway/gateway-controller/pkg/api/management/webbroker_types.go +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package management - -// WebBrokerApi defines model for WebBrokerApi. -type WebBrokerApi struct { - // ApiVersion API specification version - ApiVersion WebBrokerApiApiVersion `json:"apiVersion" yaml:"apiVersion"` - - // Kind API type - Kind WebBrokerApiKind `json:"kind" yaml:"kind"` - Metadata Metadata `json:"metadata" yaml:"metadata"` - Spec WebBrokerApiData `json:"spec" yaml:"spec"` - - // Status Server-managed lifecycle fields. Populated on responses. - Status *ResourceStatus `json:"status,omitempty" yaml:"status,omitempty"` -} - -// WebBrokerApiApiVersion API specification version -type WebBrokerApiApiVersion string - -// WebBrokerApiKind API type -type WebBrokerApiKind string - -// WebBrokerApiRequest defines model for WebBrokerApiRequest. -type WebBrokerApiRequest struct { - // ApiVersion API specification version - ApiVersion WebBrokerApiRequestApiVersion `json:"apiVersion" yaml:"apiVersion"` - - // Kind API type - Kind WebBrokerApiRequestKind `json:"kind" yaml:"kind"` - Metadata Metadata `json:"metadata" yaml:"metadata"` - Spec WebBrokerApiData `json:"spec" yaml:"spec"` -} - -// WebBrokerApiRequestApiVersion API specification version -type WebBrokerApiRequestApiVersion string - -// WebBrokerApiRequestKind API type -type WebBrokerApiRequestKind string - -// WebBrokerApiData defines spec for WebBrokerApi. -type WebBrokerApiData struct { - // Context Base path for all API routes (must start with /, no trailing slash) - Context string `json:"context" yaml:"context"` - - // DeploymentState Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment. - DeploymentState *WebBrokerApiDataDeploymentState `json:"deploymentState,omitempty" yaml:"deploymentState,omitempty"` - - // DisplayName Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed) - DisplayName string `json:"displayName" yaml:"displayName"` - - // Receiver Receiver configuration - protocol adapter for web-friendly clients (WebSocket, SSE) - Receiver WebBrokerApiReceiver `json:"receiver" yaml:"receiver"` - - // BrokerDriver Broker driver configuration - message broker adapter (Kafka, MQTT, AMQP) - BrokerDriver WebBrokerApiBrokerDriver `json:"brokerDriver" yaml:"brokerDriver"` - - // Policies API-level policies applied to all channels - Policies *WebBrokerApiPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` - - // Channels Channel-specific configurations with policies per channel - Channels map[string]WebBrokerApiChannel `json:"channels,omitempty" yaml:"channels,omitempty"` - - // Version Semantic version of the API - Version string `json:"version" yaml:"version"` - - // Vhosts Custom virtual hosts/domains for the API - Vhosts *struct { - // Main Custom virtual host/domain for production traffic - Main string `json:"main" yaml:"main"` - - // Sandbox Custom virtual host/domain for sandbox traffic - Sandbox *string `json:"sandbox,omitempty" yaml:"sandbox,omitempty"` - } `json:"vhosts,omitempty" yaml:"vhosts,omitempty"` -} - -// WebBrokerApiDataDeploymentState Desired deployment state -type WebBrokerApiDataDeploymentState string - -// WebBrokerApiReceiver Receiver configuration for protocol adapter -type WebBrokerApiReceiver struct { - // Name Receiver instance name - Name string `json:"name" yaml:"name"` - - // Type Receiver type (websocket, sse) - Type string `json:"type" yaml:"type"` - - // Properties Receiver-specific configuration properties - Properties *map[string]interface{} `json:"properties,omitempty" yaml:"properties,omitempty"` -} - -// WebBrokerApiBrokerDriver Broker driver configuration -type WebBrokerApiBrokerDriver struct { - // Name Broker driver instance name - Name string `json:"name" yaml:"name"` - - // Type Broker driver type (kafka, mqtt, amqp) - Type string `json:"type" yaml:"type"` - - // Properties Broker-specific configuration properties (bootstrap.servers, etc.) - Properties map[string]interface{} `json:"properties" yaml:"properties"` -} - -// WebBrokerApiPolicies Protocol mediation policies -type WebBrokerApiPolicies struct { - // OnConnectionInit Policies applied during WebSocket handshake - OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"onConnectionInit,omitempty" yaml:"onConnectionInit,omitempty"` - - // OnProduce Policies applied when client sends message to broker - OnProduce *[]Policy `json:"onProduce,omitempty" yaml:"onProduce,omitempty"` - - // OnConsume Policies applied when broker message delivered to client - OnConsume *[]Policy `json:"onConsume,omitempty" yaml:"onConsume,omitempty"` -} - -// WebBrokerApiConnectionInitPolicies Connection initialization policies -type WebBrokerApiConnectionInitPolicies struct { - // Request Policies applied before WebSocket upgrade - Request *[]Policy `json:"request,omitempty" yaml:"request,omitempty"` - - // Response Policies applied after WebSocket upgrade - Response *[]Policy `json:"response,omitempty" yaml:"response,omitempty"` -} - -// WebBrokerApiChannel Channel-specific configuration with policies -type WebBrokerApiChannel struct { - // Policies Channel-specific policies - Policies *WebBrokerApiChannelPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` -} - -// WebBrokerApiChannelPolicies defines policies for a specific channel -type WebBrokerApiChannelPolicies struct { - // OnConnectionInit Policies applied during WebSocket handshake for this channel - OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"onConnectionInit,omitempty" yaml:"onConnectionInit,omitempty"` - - // OnProduce Policies applied when client sends message to broker on this channel - OnProduce *[]Policy `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` - - // OnConsume Policies applied when broker message delivered to client on this channel - OnConsume *[]Policy `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` -} diff --git a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go index 4648a95ab..946592b10 100644 --- a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go +++ b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go @@ -201,7 +201,7 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke } } - // Build channels map with channel-specific policies + // Build channels map with channel-specific policies and topic mappings channels := make(map[string]interface{}) if spec.Channels != nil { for channelName, channelConfig := range spec.Channels { @@ -227,7 +227,25 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke } } - channels[channelName] = map[string]interface{}{ + // Build channel entry with policies nested inside "policies" field + channelEntry := map[string]interface{}{} + + // Add produceTo topic mapping if specified + if channelConfig.ProduceTo != nil { + channelEntry["produce_to"] = map[string]interface{}{ + "topic": channelConfig.ProduceTo.Topic, + } + } + + // Add consumeFrom topic mapping if specified + if channelConfig.ConsumeFrom != nil { + channelEntry["consume_from"] = map[string]interface{}{ + "topic": channelConfig.ConsumeFrom.Topic, + } + } + + // Nest all policies inside a "policies" field + channelEntry["policies"] = map[string]interface{}{ "on_connection_init": map[string]interface{}{ "request": channelOnConnectionInitRequest, "response": channelOnConnectionInitResponse, @@ -235,6 +253,8 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke "on_produce": channelOnProduce, "on_consume": channelOnConsume, } + + channels[channelName] = channelEntry } } From 71fc0c1332e645ef65f865a98f0061626d2cec4e Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 13:20:49 +0530 Subject: [PATCH 08/20] Resolve merge conflicts related to WebSubApi Unsubscribe changes --- .../gateway-runtime/internal/binding/types.go | 7 +++-- .../internal/xdsclient/handler.go | 30 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/event-gateway/gateway-runtime/internal/binding/types.go b/event-gateway/gateway-runtime/internal/binding/types.go index d68da36ba..6e085c0f2 100644 --- a/event-gateway/gateway-runtime/internal/binding/types.go +++ b/event-gateway/gateway-runtime/internal/binding/types.go @@ -127,9 +127,10 @@ type BrokerDriverSpec struct { // - Inbound: applied when an event is published via the webhook receiver (data ingress). // - Outbound: applied when an event is delivered to a subscriber callback (data delivery). type PolicyBindings struct { - Subscribe []PolicyRef `yaml:"subscribe"` - Inbound []PolicyRef `yaml:"inbound"` - Outbound []PolicyRef `yaml:"outbound"` + Subscribe []PolicyRef `yaml:"subscribe"` + Unsubscribe []PolicyRef `yaml:"unsubscribe"` + Inbound []PolicyRef `yaml:"inbound"` + Outbound []PolicyRef `yaml:"outbound"` } // PolicyRef references a policy to include in a chain. diff --git a/event-gateway/gateway-runtime/internal/xdsclient/handler.go b/event-gateway/gateway-runtime/internal/xdsclient/handler.go index cae04d703..cd693c169 100644 --- a/event-gateway/gateway-runtime/internal/xdsclient/handler.go +++ b/event-gateway/gateway-runtime/internal/xdsclient/handler.go @@ -105,11 +105,12 @@ type BrokerDriverEntry struct { Properties map[string]interface{} `json:"properties"` } -// PoliciesEntry holds the 3-phase policy references. +// PoliciesEntry holds the 4-phase policy references. type PoliciesEntry struct { - Subscribe []PolicyEntry `json:"subscribe"` - Inbound []PolicyEntry `json:"inbound"` - Outbound []PolicyEntry `json:"outbound"` + Subscribe []PolicyEntry `json:"subscribe"` + Unsubscribe []PolicyEntry `json:"unsubscribe"` + Inbound []PolicyEntry `json:"inbound"` + Outbound []PolicyEntry `json:"outbound"` } // PolicyEntry represents one policy reference. @@ -244,6 +245,9 @@ func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApi if subIface, ok := policiesIface["subscribe"].([]interface{}); ok { ch.Policies.Subscribe = mapGenericPolicyEntryList(subIface) } + if unsubIface, ok := policiesIface["unsubscribe"].([]interface{}); ok { + ch.Policies.Unsubscribe = mapGenericPolicyEntryList(unsubIface) + } if inIface, ok := policiesIface["inbound"].([]interface{}); ok { ch.Policies.Inbound = mapGenericPolicyEntryList(inIface) } @@ -262,9 +266,10 @@ func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApi channels[i] = binding.ChannelDef{ Name: ch.Name, Policies: binding.PolicyBindings{ - Subscribe: mapPolicyEntries(ch.Policies.Subscribe), - Inbound: mapPolicyEntries(ch.Policies.Inbound), - Outbound: mapPolicyEntries(ch.Policies.Outbound), + Subscribe: mapPolicyEntries(ch.Policies.Subscribe), + Unsubscribe: mapPolicyEntries(ch.Policies.Unsubscribe), + Inbound: mapPolicyEntries(ch.Policies.Inbound), + Outbound: mapPolicyEntries(ch.Policies.Outbound), }, } } @@ -276,6 +281,9 @@ func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApi if subIface, ok := policiesMap["subscribe"].([]interface{}); ok { policies.Subscribe = mapGenericPolicyEntryList(subIface) } + if unsubIface, ok := policiesMap["unsubscribe"].([]interface{}); ok { + policies.Unsubscribe = mapGenericPolicyEntryList(unsubIface) + } if inIface, ok := policiesMap["inbound"].([]interface{}); ok { policies.Inbound = mapGenericPolicyEntryList(inIface) } @@ -286,6 +294,7 @@ func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApi } subscribe := mapPolicyEntries(policies.Subscribe) + unsubscribe := mapPolicyEntries(policies.Unsubscribe) inbound := mapPolicyEntries(policies.Inbound) outbound := mapPolicyEntries(policies.Outbound) @@ -301,9 +310,10 @@ func (h *Handler) toWebSubApiBinding(ecr EventChannelResource) binding.WebSubApi }, BrokerDriver: h.resolveBrokerDriver(ecr.BrokerDriver), Policies: binding.PolicyBindings{ - Subscribe: subscribe, - Inbound: inbound, - Outbound: outbound, + Subscribe: subscribe, + Unsubscribe: unsubscribe, + Inbound: inbound, + Outbound: outbound, }, } } From 45fdf4d7a6bab80e6febb54d2753c4a6daf682a8 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 14:48:01 +0530 Subject: [PATCH 09/20] Revert go.mod and go.sum changes --- cli/it/go.mod | 1 - cli/it/go.sum | 3 +- common/go.mod | 10 +- common/go.sum | 15 +- event-gateway/gateway-builder/go.mod | 8 +- event-gateway/gateway-builder/go.sum | 19 +-- event-gateway/gateway-runtime/go.mod | 2 +- event-gateway/gateway-runtime/go.sum | 3 +- event-gateway/policies/map-topic/go.mod | 5 + .../sample-policies/map-topic/go.mod | 5 - gateway/gateway-builder/go.mod | 4 +- gateway/gateway-builder/go.sum | 15 +- gateway/gateway-controller/go.mod | 8 +- gateway/gateway-controller/go.sum | 32 ++-- gateway/it/go.mod | 27 ++-- gateway/it/go.sum | 47 ++++-- go.work.sum | 24 +-- kubernetes/gateway-operator/go.mod | 69 +++----- kubernetes/gateway-operator/go.sum | 149 ++++++++++++------ platform-api/src/go.mod | 12 +- platform-api/src/go.sum | 18 ++- sdk/ai/go.mod | 62 ++++---- sdk/ai/go.sum | 99 +++++++----- tests/mock-servers/mock-platform-api/go.mod | 5 +- tests/mock-servers/mock-platform-api/go.sum | 7 +- 25 files changed, 332 insertions(+), 317 deletions(-) create mode 100644 event-gateway/policies/map-topic/go.mod delete mode 100644 event-gateway/sample-policies/map-topic/go.mod diff --git a/cli/it/go.mod b/cli/it/go.mod index f85180eef..10e5c0cc0 100644 --- a/cli/it/go.mod +++ b/cli/it/go.mod @@ -15,6 +15,5 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/spf13/pflag v1.0.10 // indirect ) diff --git a/cli/it/go.sum b/cli/it/go.sum index 293ca4c35..3f2d7756e 100644 --- a/cli/it/go.sum +++ b/cli/it/go.sum @@ -25,8 +25,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -34,7 +34,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= diff --git a/common/go.mod b/common/go.mod index 3e0b41d52..d863e58c9 100644 --- a/common/go.mod +++ b/common/go.mod @@ -12,7 +12,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/mattn/go-sqlite3 v1.14.41 github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.48.0 + golang.org/x/crypto v0.47.0 ) require ( @@ -47,10 +47,10 @@ require ( github.com/ugorji/go/codec v1.3.1 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/common/go.sum b/common/go.sum index 7d8be5a72..5cddad694 100644 --- a/common/go.sum +++ b/common/go.sum @@ -107,12 +107,17 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/event-gateway/gateway-builder/go.mod b/event-gateway/gateway-builder/go.mod index 9ea1dc02b..13526c876 100644 --- a/event-gateway/gateway-builder/go.mod +++ b/event-gateway/gateway-builder/go.mod @@ -3,12 +3,6 @@ module github.com/wso2/api-platform/event-gateway/gateway-builder go 1.24.5 require ( - golang.org/x/mod v0.33.0 + golang.org/x/mod v0.32.0 gopkg.in/yaml.v3 v3.0.1 ) - -require ( - github.com/kr/pretty v0.3.1 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect -) diff --git a/event-gateway/gateway-builder/go.sum b/event-gateway/gateway-builder/go.sum index 4a0f66cbe..3bf5aa33b 100644 --- a/event-gateway/gateway-builder/go.sum +++ b/event-gateway/gateway-builder/go.sum @@ -1,19 +1,6 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/event-gateway/gateway-runtime/go.mod b/event-gateway/gateway-runtime/go.mod index 0abf299ae..fe2f29b4d 100644 --- a/event-gateway/gateway-runtime/go.mod +++ b/event-gateway/gateway-runtime/go.mod @@ -5,7 +5,7 @@ go 1.26.2 require ( github.com/envoyproxy/go-control-plane/envoy v1.36.0 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 + github.com/gorilla/websocket v1.5.3 github.com/knadh/koanf/parsers/toml/v2 v2.2.0 github.com/knadh/koanf/providers/env v1.1.0 github.com/knadh/koanf/providers/file v1.2.1 diff --git a/event-gateway/gateway-runtime/go.sum b/event-gateway/gateway-runtime/go.sum index e5119325a..77b917475 100644 --- a/event-gateway/gateway-runtime/go.sum +++ b/event-gateway/gateway-runtime/go.sum @@ -32,7 +32,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= diff --git a/event-gateway/policies/map-topic/go.mod b/event-gateway/policies/map-topic/go.mod new file mode 100644 index 000000000..0aef3fdb5 --- /dev/null +++ b/event-gateway/policies/map-topic/go.mod @@ -0,0 +1,5 @@ +module github.com/wso2/api-platform/event-gateway/policies/map-topic + +go 1.26.2 + +require github.com/wso2/api-platform/sdk/core v0.2.9 diff --git a/event-gateway/sample-policies/map-topic/go.mod b/event-gateway/sample-policies/map-topic/go.mod deleted file mode 100644 index 15d3586b8..000000000 --- a/event-gateway/sample-policies/map-topic/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/wso2/api-platform/event-gateway/sample-policies/map-topic - -go 1.26.2 - -require github.com/wso2/api-platform/sdk/core v0.2.9 diff --git a/gateway/gateway-builder/go.mod b/gateway/gateway-builder/go.mod index 322da0cd3..1144a9e24 100644 --- a/gateway/gateway-builder/go.mod +++ b/gateway/gateway-builder/go.mod @@ -5,14 +5,12 @@ go 1.26.2 require ( github.com/stretchr/testify v1.11.1 github.com/wso2/api-platform/sdk/core v0.2.9 - golang.org/x/mod v0.33.0 + golang.org/x/mod v0.32.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/gateway/gateway-builder/go.sum b/gateway/gateway-builder/go.sum index e687fcf9f..8dd8a86ba 100644 --- a/gateway/gateway-builder/go.sum +++ b/gateway/gateway-builder/go.sum @@ -1,25 +1,18 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/wso2/api-platform/sdk/core v0.2.9 h1:3lvAsMlLhy8nNgPL24/UFS/f4sq5e+XpryA4L1PO7dU= github.com/wso2/api-platform/sdk/core v0.2.9/go.mod h1:vgNVzR16g9k5cun3VXZ7wDg8UGbPxsVU2TW8EbRCv0o= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/gateway/gateway-controller/go.mod b/gateway/gateway-controller/go.mod index e46980d28..b4546a837 100644 --- a/gateway/gateway-controller/go.mod +++ b/gateway/gateway-controller/go.mod @@ -9,7 +9,7 @@ require ( github.com/gin-gonic/gin v1.11.0 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 + github.com/gorilla/websocket v1.5.3 github.com/jackc/pgx/v5 v5.9.2 github.com/jmoiron/sqlx v1.4.0 github.com/knadh/koanf/parsers/toml/v2 v2.2.0 @@ -88,8 +88,6 @@ require ( github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - go.opentelemetry.io/otel v1.41.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/arch v0.23.0 // indirect golang.org/x/crypto v0.48.0 // indirect @@ -98,8 +96,8 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect ) replace github.com/wso2/api-platform/common => ../../common diff --git a/gateway/gateway-controller/go.sum b/gateway/gateway-controller/go.sum index 26abe94f4..67374bd12 100644 --- a/gateway/gateway-controller/go.sum +++ b/gateway/gateway-controller/go.sum @@ -81,8 +81,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -200,16 +200,16 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= -go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= -go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= -go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= -go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= -go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= -go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -233,10 +233,10 @@ golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/gateway/it/go.mod b/gateway/it/go.mod index 6ebbaed7e..bafae2a33 100644 --- a/gateway/it/go.mod +++ b/gateway/it/go.mod @@ -67,7 +67,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect - github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsevents v0.2.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect @@ -77,21 +77,21 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -138,8 +138,6 @@ require ( github.com/oapi-codegen/runtime v1.1.2 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/onsi/ginkgo/v2 v2.22.0 // indirect - github.com/onsi/gomega v1.36.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect @@ -154,7 +152,7 @@ require ( github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect @@ -212,15 +210,14 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.3 // indirect - k8s.io/apimachinery v0.33.3 // indirect - k8s.io/client-go v0.33.3 // indirect + k8s.io/api v0.32.3 // indirect + k8s.io/apimachinery v0.32.3 // indirect + k8s.io/client-go v0.32.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect - sigs.k8s.io/yaml v1.5.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect tags.cncf.io/container-device-interface v1.0.1 // indirect ) diff --git a/gateway/it/go.sum b/gateway/it/go.sum index 86b07c5e6..da79dfa90 100644 --- a/gateway/it/go.sum +++ b/gateway/it/go.sum @@ -1,6 +1,5 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= @@ -166,7 +165,8 @@ github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0o github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= -github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -191,13 +191,16 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -227,13 +230,16 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -243,7 +249,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -383,11 +390,14 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtE github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -447,7 +457,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA= github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU= @@ -695,19 +706,23 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= -k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= -k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0= diff --git a/go.work.sum b/go.work.sum index fc7b92a32..2313c01f3 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2523,10 +2523,7 @@ github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= @@ -2681,7 +2678,6 @@ github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0/go.mod h1:KgnwoLYCZ8I github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= @@ -2817,6 +2813,7 @@ github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgj github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= @@ -3123,13 +3120,11 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= @@ -3245,7 +3240,6 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -3296,7 +3290,6 @@ github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSW github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= @@ -3504,7 +3497,6 @@ go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRl go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/jaeger v1.13.0 h1:VAMoGujbVV8Q0JNM/cEbhzUIWWBxnEqH45HP9iBKN04= go.opentelemetry.io/otel/exporters/jaeger v1.13.0/go.mod h1:fHwbmle6mBFJA1p2ZIhilvffCdq/dM5UTIiCOmEjS+w= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= @@ -3541,7 +3533,6 @@ go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9 go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= @@ -3559,7 +3550,6 @@ go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJ go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= @@ -3568,7 +3558,6 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRY go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= @@ -3587,7 +3576,6 @@ go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37Cb go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -3614,7 +3602,6 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -3765,7 +3752,6 @@ golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3854,7 +3840,6 @@ golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -3904,7 +3889,6 @@ golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -4036,7 +4020,6 @@ golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= @@ -4117,7 +4100,6 @@ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -4624,7 +4606,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go. google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:+34luvCflYKiKylNwGJfn9cFBbcL/WrkciMmDmsTQ/A= @@ -4772,7 +4753,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -4886,7 +4866,6 @@ google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/avro.v0 v0.0.0-20171217001914-a730b5802183 h1:PGIdqvwfpMUyUP+QAlAnKTSWQ671SmYjoou2/5j7HXk= @@ -4957,7 +4936,6 @@ k8s.io/kubelet v0.32.3/go.mod h1:yyAQSCKC+tjSlaFw4HQG7Jein+vo+GeKBGdXdQGvL1U= k8s.io/metrics v0.33.3 h1:9CcqBz15JZfISqwca33gdHS8I6XfsK1vA8WUdEnG70g= k8s.io/metrics v0.33.3/go.mod h1:Aw+cdg4AYHw0HvUY+lCyq40FOO84awrqvJRTw0cmXDs= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= kernel.org/pub/linux/libs/security/libcap/cap v1.2.76 h1:mrdLPj8ujM6eIKGtd1PkkuCIodpFFDM42Cfm0YODkIM= kernel.org/pub/linux/libs/security/libcap/cap v1.2.76/go.mod h1:7V2BQeHnVAQwhCnCPJ977giCeGDiywVewWF+8vkpPlc= kernel.org/pub/linux/libs/security/libcap/psx v1.2.76 h1:3DyzQ30OHt3wiOZVL1se2g1PAPJIU7+tMUyvfMUj1dY= diff --git a/kubernetes/gateway-operator/go.mod b/kubernetes/gateway-operator/go.mod index 11a6e1b7c..83e2e5df8 100644 --- a/kubernetes/gateway-operator/go.mod +++ b/kubernetes/gateway-operator/go.mod @@ -7,7 +7,7 @@ require ( github.com/knadh/koanf/parsers/yaml v1.1.0 github.com/knadh/koanf/providers/confmap v1.0.0 github.com/knadh/koanf/providers/env v1.1.0 - github.com/knadh/koanf/providers/file v1.2.1 + github.com/knadh/koanf/providers/file v1.2.0 github.com/knadh/koanf/v2 v2.3.0 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.0 @@ -23,13 +23,12 @@ require ( ) require ( - dario.cat/mergo v1.0.2 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect @@ -38,13 +37,11 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/containerd/containerd v1.7.29 // indirect - github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v1.0.0-rc.1 // indirect - github.com/creack/pty v1.1.24 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect @@ -64,7 +61,6 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect @@ -72,12 +68,11 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect @@ -87,8 +82,8 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mattn/go-sqlite3 v1.14.41 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -104,47 +99,35 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/redis/go-redis/v9 v9.17.3 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect - github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.7 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.41.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.41.0 // indirect - go.opentelemetry.io/otel/sdk v1.41.0 // indirect - go.opentelemetry.io/otel/trace v1.41.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/term v0.40.0 // indirect - golang.org/x/text v0.35.0 // indirect + go.yaml.in/yaml/v3 v3.0.3 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.12.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.79.3 // indirect - google.golang.org/protobuf v1.36.11 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/apiextensions-apiserver v0.33.3 // indirect @@ -153,7 +136,7 @@ require ( k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/kubectl v0.33.3 // indirect - k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect oras.land/oras-go/v2 v2.6.0 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/kustomize/api v0.19.0 // indirect diff --git a/kubernetes/gateway-operator/go.sum b/kubernetes/gateway-operator/go.sum index 7a8ae02c9..b53547fba 100644 --- a/kubernetes/gateway-operator/go.sum +++ b/kubernetes/gateway-operator/go.sum @@ -1,7 +1,9 @@ -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= @@ -12,7 +14,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= @@ -29,21 +32,23 @@ github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuP github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -58,7 +63,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -121,7 +127,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -137,7 +144,8 @@ github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -145,7 +153,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= +github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -154,8 +163,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -168,7 +177,8 @@ github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5z github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A= github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc= github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY= -github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM= +github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U= +github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -192,9 +202,11 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc= github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -237,17 +249,22 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -260,9 +277,13 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -284,17 +305,24 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGh go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= @@ -305,13 +333,18 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= -go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= -go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= +go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -320,24 +353,30 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -345,18 +384,22 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -364,11 +407,14 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -403,7 +449,8 @@ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUy k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/kubectl v0.33.3 h1:r/phHvH1iU7gO/l7tTjQk2K01ER7/OAJi8uFHHyWSac= k8s.io/kubectl v0.33.3/go.mod h1:euj2bG56L6kUGOE/ckZbCoudPwuj4Kud7BR0GzyNiT0= -k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= diff --git a/platform-api/src/go.mod b/platform-api/src/go.mod index bdc4f5a2c..0aaae691b 100644 --- a/platform-api/src/go.mod +++ b/platform-api/src/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.29.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 + github.com/gorilla/websocket v1.5.3 github.com/jackc/pgx/v5 v5.9.2 github.com/kelseyhightower/envconfig v1.4.0 github.com/mattn/go-sqlite3 v1.14.41 @@ -50,11 +50,11 @@ require ( github.com/ugorji/go/codec v1.3.1 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/platform-api/src/go.sum b/platform-api/src/go.sum index 339a1772f..73d1a7658 100644 --- a/platform-api/src/go.sum +++ b/platform-api/src/go.sum @@ -45,7 +45,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -118,12 +119,17 @@ go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/sdk/ai/go.mod b/sdk/ai/go.mod index 395a95567..5ad839093 100644 --- a/sdk/ai/go.mod +++ b/sdk/ai/go.mod @@ -13,57 +13,54 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cilium/ebpf v0.16.0 // indirect + github.com/cilium/ebpf v0.11.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect github.com/cockroachdb/redact v1.1.3 // indirect - github.com/containerd/cgroups/v3 v3.0.5 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/containerd/cgroups/v3 v3.0.3 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getsentry/sentry-go v0.12.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/godbus/dbus/v5 v5.0.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect - github.com/jonboulle/clockwork v0.5.0 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/mdlayher/socket v0.5.1 // indirect github.com/milvus-io/milvus-proto/go-api/v2 v2.6.8 // indirect - github.com/moby/sys/userns v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/opencontainers/runtime-spec v1.2.1 // indirect + github.com/opencontainers/runtime-spec v1.0.2 // indirect github.com/panjf2000/ants/v2 v2.11.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.23.2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/samber/lo v1.27.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -71,21 +68,21 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/tidwall/gjson v1.17.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect + github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/twpayne/go-geom v1.6.1 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.etcd.io/bbolt v1.4.3 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.etcd.io/bbolt v1.3.12 // indirect go.etcd.io/etcd/api/v3 v3.5.23 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.23 // indirect go.etcd.io/etcd/client/v2 v2.305.23 // indirect @@ -94,7 +91,7 @@ require ( go.etcd.io/etcd/raft/v3 v3.5.23 // indirect go.etcd.io/etcd/server/v3 v3.5.23 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect go.opentelemetry.io/otel v1.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 // indirect @@ -106,14 +103,13 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/sync v0.20.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.35.0 // indirect - golang.org/x/time v0.12.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/time v0.10.0 // indirect google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect @@ -123,6 +119,6 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apimachinery v0.33.3 // indirect - sigs.k8s.io/yaml v1.5.0 // indirect + k8s.io/apimachinery v0.32.3 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/sdk/ai/go.sum b/sdk/ai/go.sum index 8fa257857..910db5c88 100644 --- a/sdk/ai/go.sum +++ b/sdk/ai/go.sum @@ -24,13 +24,15 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= +github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= +github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -43,13 +45,15 @@ github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9D github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= +github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -74,7 +78,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= @@ -85,8 +90,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -97,15 +102,14 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -133,7 +137,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -147,12 +152,15 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -174,9 +182,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= @@ -221,8 +228,6 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/milvus-io/milvus-proto/go-api/v2 v2.6.8 h1:WTLaqxbwiMuAt0qnpMmnOGnkt7rnj00yrU0rlKnENmk= @@ -233,7 +238,6 @@ github.com/milvus-io/milvus/pkg/v2 v2.6.8 h1:pdFb6BVx8Ek4Ioh7t5VWO6rhZgrFxlulaU1 github.com/milvus-io/milvus/pkg/v2 v2.6.8/go.mod h1:jiC8w9PEhvpN6CkZnG1+hfL5sTmNCiY2HLl/h8d97cw= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -251,7 +255,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= @@ -270,11 +275,15 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= @@ -309,11 +318,13 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -343,7 +354,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/twpayne/go-geom v1.6.1 h1:iLE+Opv0Ihm/ABIcvQFGIiFBXd76oBIar9drAwHFhR4= github.com/twpayne/go-geom v1.6.1/go.mod h1:Kr+Nly6BswFsKM5sd31YaoWS5PeDDH2NftJTK7Gd028= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= @@ -363,7 +375,8 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= @@ -372,9 +385,10 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.3.12 h1:UAxZAIuJqzFwByP19gZC3zd5robK3FOangrGS+Fdczg= +go.etcd.io/bbolt v1.3.12/go.mod h1:Gi2toLZr1jFkuReJm+yEPn7H8wk6ooptePtHYCbCS1g= go.etcd.io/etcd/api/v3 v3.5.23 h1:tQi/RaO6peOhmf0c11miU3RQIYPZmiL3UzG9V+f8g6k= go.etcd.io/etcd/api/v3 v3.5.23/go.mod h1:QP4ZLWROP49Kk/vPLhudxYQcF4ndhMQ1gvJE4rCTAgc= go.etcd.io/etcd/client/pkg/v3 v3.5.23 h1:RzwVV28JgOwGl5TUjA47s9IWxl5qQjM2VqSh8wjFFLM= @@ -391,7 +405,8 @@ go.etcd.io/etcd/server/v3 v3.5.23 h1:2g1hz32pp1TYMI7xUyifR8gXOlUnTBCfixV+uJ8BqRU go.etcd.io/etcd/server/v3 v3.5.23/go.mod h1:sZwt/lLSwYQ/S5aAbzSQX2xNw1WA0Fo5noUCVxVYB4g= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= @@ -421,8 +436,6 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -434,7 +447,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -463,7 +477,8 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -474,7 +489,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -513,9 +529,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -601,6 +619,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/tests/mock-servers/mock-platform-api/go.mod b/tests/mock-servers/mock-platform-api/go.mod index 2723d911f..25d69b755 100644 --- a/tests/mock-servers/mock-platform-api/go.mod +++ b/tests/mock-servers/mock-platform-api/go.mod @@ -4,9 +4,9 @@ go 1.26.2 require ( github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 + github.com/gorilla/websocket v1.5.3 github.com/jackc/pgx/v5 v5.9.2 - github.com/mattn/go-sqlite3 v1.14.41 + github.com/mattn/go-sqlite3 v1.14.34 ) require ( @@ -15,7 +15,6 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - golang.org/x/net v0.51.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/text v0.35.0 // indirect ) diff --git a/tests/mock-servers/mock-platform-api/go.sum b/tests/mock-servers/mock-platform-api/go.sum index 6943909fb..2b0b85f1e 100644 --- a/tests/mock-servers/mock-platform-api/go.sum +++ b/tests/mock-servers/mock-platform-api/go.sum @@ -3,7 +3,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -12,7 +13,8 @@ github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA= +github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= +github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -21,7 +23,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= From fbab75320e3434a0886975ce9ef3def8529e0062 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 15:36:09 +0530 Subject: [PATCH 10/20] Remove the map-topic sample policy --- event-gateway/build.yaml | 2 - event-gateway/default-policies/map-topic.yaml | 33 -- .../channels-webbrokerapi-example.yaml | 59 +- .../websocket/broker_api_connector.go | 6 +- .../internal/runtime/runtime.go | 59 +- event-gateway/policies/map-topic/go.mod | 5 - .../sample-policies/map-topic/maptopic.go | 70 --- .../map-topic/policy-definition.yaml | 35 -- .../pkg/api/management/generated.go | 511 +++++++++--------- go.work | 1 - 10 files changed, 304 insertions(+), 477 deletions(-) delete mode 100644 event-gateway/default-policies/map-topic.yaml delete mode 100644 event-gateway/policies/map-topic/go.mod delete mode 100644 event-gateway/sample-policies/map-topic/maptopic.go delete mode 100644 event-gateway/sample-policies/map-topic/policy-definition.yaml diff --git a/event-gateway/build.yaml b/event-gateway/build.yaml index ddab3d7e7..dc6a43070 100644 --- a/event-gateway/build.yaml +++ b/event-gateway/build.yaml @@ -6,8 +6,6 @@ policies: gomodule: github.com/wso2/gateway-controllers/policies/set-headers@v1.0.1 - name: api-key-auth gomodule: github.com/wso2/gateway-controllers/policies/api-key-auth@v1.0.1 - - name: map-topic - filePath: ./sample-policies/map-topic - name: advanced-ratelimit gomodule: github.com/wso2/gateway-controllers/policies/advanced-ratelimit@v1 - name: analytics-header-filter diff --git a/event-gateway/default-policies/map-topic.yaml b/event-gateway/default-policies/map-topic.yaml deleted file mode 100644 index 88f543127..000000000 --- a/event-gateway/default-policies/map-topic.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# Map-topic policy for WebBrokerApi channel topic configuration -name: map-topic -version: v1.0.0 -description: | - Configures the Kafka topic for producing to or consuming from in WebBrokerApi channels. - This policy specifies the exact topic name at the broker level. - -parameters: - type: object - additionalProperties: false - properties: - mode: - type: string - x-wso2-policy-advanced-param: false - description: | - Specifies the operation mode: "produceTo" for producing messages to a topic, - or "consumeFrom" for consuming messages from a topic. - enum: - - produceTo - - consumeFrom - - topic: - type: string - x-wso2-policy-advanced-param: false - description: | - The Kafka topic name to produce to or consume from. - minLength: 1 - maxLength: 256 - pattern: "^[a-zA-Z0-9._-]+$" - - required: - - mode - - topic diff --git a/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml b/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml index 5e43eb774..30a1bd280 100644 --- a/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml +++ b/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml @@ -14,31 +14,44 @@ channels: broker-driver: type: kafka properties: - topic: repo-events bootstrap.servers: localhost:9092 security.protocol: PLAINTEXT - allChannelPolicies: - on_connection_init: - request: - - name: api-key-auth - version: v1 - params: - in: header - name: X-API-Key - response: [] - on_produce: - - name: map-topics - version: v1 - params: - extraction: - source: header - key: X-Client-Topic - mappings: - client-issues-topic: kafka-repo-issues - client-commits-topic: kafka-repo-commits - client-pr-topic: kafka-repo-pull-requests - defaultTopic: kafka-repo-events - on_consume: [] + # Channel definitions with topic mappings + channels: + issues: + produceTo: + topic: kafka-repo-issues + consumeFrom: + topic: kafka-repo-issues + policies: + on_connection_init: + request: + - name: api-key-auth + version: v1 + params: + in: header + name: X-API-Key + response: [] + on_produce: [] + on_consume: [] + commits: + produceTo: + topic: kafka-repo-commits + consumeFrom: + topic: kafka-repo-commits + policies: + on_connection_init: {} + on_produce: [] + on_consume: [] + pull-requests: + produceTo: + topic: kafka-repo-pull-requests + consumeFrom: + topic: kafka-repo-pull-requests + policies: + on_connection_init: {} + on_produce: [] + on_consume: [] # WebSubApi example (for comparison) - kind: WebSubApi diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go index a0429a04b..9e46259eb 100644 --- a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -434,12 +434,8 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC continue } - // Determine target topic from channel config first, then fallback to policy-set topic. + // Determine target topic from channel config targetTopic := conn.produceTopic - if targetTopic == "" { - // Fallback: check if policy set a topic (legacy map-topic policy) - targetTopic = processed.Topic - } if targetTopic == "" { // Final fallback: normalized channel name targetTopic = binding.NormalizeTopicSegment(conn.channelName) diff --git a/event-gateway/gateway-runtime/internal/runtime/runtime.go b/event-gateway/gateway-runtime/internal/runtime/runtime.go index 196f45bd1..46a5a9362 100644 --- a/event-gateway/gateway-runtime/internal/runtime/runtime.go +++ b/event-gateway/gateway-runtime/internal/runtime/runtime.go @@ -811,27 +811,15 @@ func (r *Runtime) buildWebBrokerApiPolicyChains(wbb binding.WebBrokerApiBinding, return connInitReqKey, connInitRespKey, produceKey, consumeKey, nil } -// extractTopicsFromChannelPolicies extracts Kafka topics to subscribe to from a channel's on_consume policies. -// Only extracts topics from map-topic policies with mode="consumeFrom". -// If no policies specify topics, defaults to the channel name itself. +// extractTopicsFromChannelPolicies extracts Kafka topics to subscribe to from a channel's consumeFrom config. +// If consumeFrom is not specified, defaults to the normalized channel name. // These topics are used for Kafka consumer subscription. func extractTopicsFromChannelPolicies(channelName string, channelDef binding.WebBrokerChannelDef) []string { topics := make(map[string]bool) // Use map to deduplicate - // Check consumeFrom field first (new approach) + // Check consumeFrom field if channelDef.ConsumeFrom != nil && channelDef.ConsumeFrom.Topic != "" { topics[channelDef.ConsumeFrom.Topic] = true - } else { - // Fallback: check onConsume policies for map-topic (legacy) - for _, policy := range channelDef.OnConsume { - if policy.Name == "map-topic" && policy.Params != nil { - if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { - if topic, ok := policy.Params["topic"].(string); ok && topic != "" { - topics[topic] = true - } - } - } - } } // If no consumeFrom topics found, default to normalized channel name @@ -847,52 +835,29 @@ func extractTopicsFromChannelPolicies(channelName string, channelDef binding.Web return result } -// extractAllTopicsFromChannelPolicies extracts ALL Kafka topics (both produce and consume) from a channel. -// If no policies specify topics, defaults to the channel name itself for both produce and consume. +// extractAllTopicsFromChannelPolicies extracts ALL Kafka topics (both produce and consume) from a channel's config. +// If produceTo/consumeFrom are not specified, defaults to the normalized channel name. // Used to ensure all necessary topics exist in Kafka before the API starts. func extractAllTopicsFromChannelPolicies(channelName string, channelDef binding.WebBrokerChannelDef) []string { topics := make(map[string]bool) // Use map to deduplicate hasProduceTopics := false hasConsumeTopics := false - // Check produceTo field first (new approach) + // Check produceTo field if channelDef.ProduceTo != nil && channelDef.ProduceTo.Topic != "" { topics[channelDef.ProduceTo.Topic] = true hasProduceTopics = true - } else { - // Fallback: check onProduce policies for map-topic (legacy) - for _, policy := range channelDef.OnProduce { - if policy.Name == "map-topic" && policy.Params != nil { - if mode, ok := policy.Params["mode"].(string); ok && mode == "produceTo" { - if topic, ok := policy.Params["topic"].(string); ok && topic != "" { - topics[topic] = true - hasProduceTopics = true - } - } - } - } } - // Check consumeFrom field first (new approach) + // Check consumeFrom field if channelDef.ConsumeFrom != nil && channelDef.ConsumeFrom.Topic != "" { topics[channelDef.ConsumeFrom.Topic] = true hasConsumeTopics = true - } else { - // Fallback: check onConsume policies for map-topic (legacy) - for _, policy := range channelDef.OnConsume { - if policy.Name == "map-topic" && policy.Params != nil { - if mode, ok := policy.Params["mode"].(string); ok && mode == "consumeFrom" { - if topic, ok := policy.Params["topic"].(string); ok && topic != "" { - topics[topic] = true - hasConsumeTopics = true - } - } - } - } - // If still no consume topics found, use normalized channel name as default - if !hasConsumeTopics { - topics[binding.NormalizeTopicSegment(channelName)] = true - } + } + + // If no consume topics found, use normalized channel name as default + if !hasConsumeTopics { + topics[binding.NormalizeTopicSegment(channelName)] = true } // If no produce topics were found, also add normalized channel name for producing diff --git a/event-gateway/policies/map-topic/go.mod b/event-gateway/policies/map-topic/go.mod deleted file mode 100644 index 0aef3fdb5..000000000 --- a/event-gateway/policies/map-topic/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/wso2/api-platform/event-gateway/policies/map-topic - -go 1.26.2 - -require github.com/wso2/api-platform/sdk/core v0.2.9 diff --git a/event-gateway/sample-policies/map-topic/maptopic.go b/event-gateway/sample-policies/map-topic/maptopic.go deleted file mode 100644 index e96457fbf..000000000 --- a/event-gateway/sample-policies/map-topic/maptopic.go +++ /dev/null @@ -1,70 +0,0 @@ -package maptopic - -import ( - "context" - "log/slog" - - policy "github.com/wso2/api-platform/sdk/core/policy/v1alpha2" -) - -// MapTopicPolicy routes messages to specific Kafka topics in WebBrokerApi channels. -// For mode="produceTo": Sets the target topic on outbound messages -// For mode="consumeFrom": Provides metadata for Kafka consumer subscription (no processing) -type MapTopicPolicy struct { - mode string - topic string -} - -// GetPolicy returns the policy instance. -func GetPolicy( - metadata policy.PolicyMetadata, - params map[string]interface{}, -) (policy.Policy, error) { - mode := "" - topic := "" - - if modeVal, ok := params["mode"]; ok { - if modeStr, ok := modeVal.(string); ok { - mode = modeStr - } - } - - if topicVal, ok := params["topic"]; ok { - if topicStr, ok := topicVal.(string); ok { - topic = topicStr - } - } - - slog.Debug("[Map Topic]: Policy created", "mode", mode, "topic", topic) - return &MapTopicPolicy{mode: mode, topic: topic}, nil -} - -// Mode returns the processing mode for this policy. -func (p *MapTopicPolicy) Mode() policy.ProcessingMode { - if p.mode == "produceTo" { - // For produceTo mode, we need to process the request body to set the topic - return policy.ProcessingMode{ - RequestHeaderMode: policy.HeaderModeSkip, - RequestBodyMode: policy.BodyModeBuffer, // Need to set topic on message - ResponseHeaderMode: policy.HeaderModeSkip, - ResponseBodyMode: policy.BodyModeSkip, - } - } - // For consumeFrom mode, this is metadata-only, skip all processing - return policy.ProcessingMode{ - RequestHeaderMode: policy.HeaderModeSkip, - RequestBodyMode: policy.BodyModeSkip, - ResponseHeaderMode: policy.HeaderModeSkip, - ResponseBodyMode: policy.BodyModeSkip, - } -} - -// OnRequestBody sets the Kafka topic for produceTo mode. -func (p *MapTopicPolicy) OnRequestBody(_ context.Context, ctx *policy.RequestContext, params map[string]interface{}) policy.RequestAction { - if p.mode == "produceTo" && p.topic != "" { - // Store the topic in metadata so the hub can extract it and apply to the message - ctx.Metadata["kafka.topic"] = p.topic - slog.Debug("[Map Topic]: Set target topic in metadata", "topic", p.topic) - } - return nil -} diff --git a/event-gateway/sample-policies/map-topic/policy-definition.yaml b/event-gateway/sample-policies/map-topic/policy-definition.yaml deleted file mode 100644 index 92f5c0797..000000000 --- a/event-gateway/sample-policies/map-topic/policy-definition.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: map-topic -version: v1.0.0 -displayName: Map Topic -description: | - Configures the Kafka topic for producing to or consuming from in WebBrokerApi channels. - This policy specifies the exact topic name at the broker level for channel-based routing. - -parameters: - type: object - additionalProperties: false - properties: - mode: - type: string - description: | - Specifies the operation mode: "produceTo" for producing messages to a topic, - or "consumeFrom" for consuming messages from a topic. - enum: - - produceTo - - consumeFrom - - topic: - type: string - description: | - The Kafka topic name to produce to or consume from. - minLength: 1 - maxLength: 256 - pattern: "^[a-zA-Z0-9._-]+$" - - required: - - mode - - topic - -systemParameters: - type: object - properties: {} diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index 2f13dc9ef..7f1e505c9 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -4826,262 +4826,261 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/go.work b/go.work index f256f58ce..bad1582c0 100644 --- a/go.work +++ b/go.work @@ -6,7 +6,6 @@ use ( ./common ./event-gateway/gateway-builder ./event-gateway/gateway-runtime - ./event-gateway/sample-policies/map-topic ./event-gateway/webhook-listener ./gateway/gateway-builder ./gateway/gateway-controller From ffab0de3cc375bbba7168173ac04be693a71d2d1 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 17:36:03 +0530 Subject: [PATCH 11/20] Update WebBroker API Spec --- docs/rest-apis/gateway/schemas.md | 638 +++++++++--------- .../gateway/webbroker-api-management.md | 132 ++-- .../gateway/websub-api-management.md | 21 +- .../internal/xdsclient/handler.go | 24 +- .../api/management-openapi.yaml | 108 ++- .../pkg/api/management/generated.go | 580 ++++++++-------- .../pkg/policyxds/event_channel_translator.go | 81 +-- 7 files changed, 811 insertions(+), 773 deletions(-) diff --git a/docs/rest-apis/gateway/schemas.md b/docs/rest-apis/gateway/schemas.md index 91629e964..f651e636c 100644 --- a/docs/rest-apis/gateway/schemas.md +++ b/docs/rest-apis/gateway/schemas.md @@ -648,18 +648,16 @@ xor "main": "api.example.com", "sandbox": "sandbox-api.example.com" }, - "receiver": { - "policies": [ + "policies": { + "on_subscription": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ] - }, - "hub": { - "policies": [ + ], + "on_unsubscription": [ { "name": "cors", "version": "v1", @@ -667,10 +665,51 @@ xor "params": {} } ], - "channels": [ + "on_message_received": [ { - "name": "issues", - "policies": [ + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_delivery": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "channels": { + "property1": { + "policies": { + "on_subscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_unsubscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_received": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_delivery": [ { "name": "cors", "version": "v1", @@ -679,17 +718,43 @@ xor } ] } - ] - }, - "delivery": { - "policies": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} + }, + "property2": { + "policies": { + "on_subscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_unsubscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_received": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_delivery": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] } - ] + } }, "deploymentState": "deployed" } @@ -706,9 +771,9 @@ xor |vhosts|object|false|none|Custom virtual hosts/domains for the API| |» main|string|true|none|Custom virtual host/domain for production traffic| |» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| -|receiver|[WebSubReceiver](#schemawebsubreceiver)|false|none|Receiver configuration for the WebSub API - handles inbound event publishing from publishers.| -|hub|[WebSubHub](#schemawebsubhub)|true|none|Hub configuration for the WebSub API - handles subscriber management and event fan-out.| -|delivery|[WebSubDelivery](#schemawebsubdelivery)|false|none|Delivery configuration for the WebSub API - handles outbound event delivery to subscribers.| +|policies|[WebSubAllChannelPolicies](#schemawebsuballchannelpolicies)|false|none|Policies applied to all channels, organized by event type.| +|channels|object|false|none|Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel.| +|» **additionalProperties**|[WebSubChannel](#schemawebsubchannel)|false|none|A single channel definition with optional per-channel policy overrides.| |deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment.| #### Enumerated Values @@ -718,74 +783,79 @@ xor |deploymentState|deployed| |deploymentState|undeployed| -

WebSubReceiver

+

WebSubChannel

- - - - + + + + ```json { - "policies": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] + "policies": { + "on_subscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_unsubscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_received": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_delivery": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } } ``` -Receiver configuration for the WebSub API - handles inbound event publishing from publishers. +A single channel definition with optional per-channel policy overrides. ### Properties |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied to inbound webhook requests (e.g., hmac-signature-validation)| +|policies|[WebSubChannelPolicies](#schemawebsubchannelpolicies)|false|none|Policies applied to a specific channel, organized by event type.| -

WebSubDelivery

+

WebSubAllChannelPolicies

- - - - + + + + ```json { - "policies": [ + "on_subscription": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ] -} - -``` - -Delivery configuration for the WebSub API - handles outbound event delivery to subscribers. - -### Properties - -|Name|Type|Required|Restrictions|Description| -|---|---|---|---|---| -|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied when delivering events to subscriber callback URLs (e.g., hmac-signature-validation)| - -

WebSubHub

- - - - - - -```json -{ - "policies": [ + ], + "on_unsubscription": [ { "name": "cors", "version": "v1", @@ -793,43 +863,71 @@ Delivery configuration for the WebSub API - handles outbound event delivery to s "params": {} } ], - "channels": [ + "on_message_received": [ { - "name": "issues", - "policies": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_delivery": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} } ] } ``` -Hub configuration for the WebSub API - handles subscriber management and event fan-out. +Policies applied to all channels, organized by event type. ### Properties |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied at the hub level (e.g., api-key-auth for subscribers)| -|channels|[[HubChannel](#schemahubchannel)]|true|none|List of topic channels available for subscription| +|on_subscription|[[Policy](#schemapolicy)]|false|none|Policies applied when a client subscribes to a channel (e.g., api-key-auth)| +|on_unsubscription|[[Policy](#schemapolicy)]|false|none|Policies applied when a client unsubscribes from a channel (e.g., api-key-auth)| +|on_message_received|[[Policy](#schemapolicy)]|false|none|Policies applied when a message is received from the publisher via webhook (e.g., hmac-signature-validation)| +|on_message_delivery|[[Policy](#schemapolicy)]|false|none|Policies applied when delivering a message to a subscriber callback URL (e.g., hmac-sign-messages)| -

HubChannel

+

WebSubChannelPolicies

- - - - + + + + ```json { - "name": "issues", - "policies": [ + "on_subscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_unsubscription": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_received": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ], + "on_message_delivery": [ { "name": "cors", "version": "v1", @@ -841,14 +939,16 @@ Hub configuration for the WebSub API - handles subscriber management and event f ``` -A subscribable topic channel within the WebSub hub. +Policies applied to a specific channel, organized by event type. ### Properties |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|name|string|true|none|Channel name or topic identifier relative to API context.| -|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied only to this channel (e.g., rbac)| +|on_subscription|[[Policy](#schemapolicy)]|false|none|Policies applied when a client subscribes to this channel (e.g., rbac)| +|on_unsubscription|[[Policy](#schemapolicy)]|false|none|Policies applied when a client unsubscribes from this channel| +|on_message_received|[[Policy](#schemapolicy)]|false|none|Policies applied when a message is received for this channel| +|on_message_delivery|[[Policy](#schemapolicy)]|false|none|Policies applied when delivering a message for this channel|

Channel

@@ -911,7 +1011,7 @@ Channel (topic/event stream) definition for async APIs. "name": "websocket-receiver", "type": "websocket" }, - "brokerDriver": { + "broker": { "name": "kafka-driver", "type": "kafka", "properties": { @@ -921,6 +1021,17 @@ Channel (topic/event stream) definition for async APIs. ] } }, + "allChannels": { + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + }, "channels": { "prices": { "produceTo": { @@ -929,13 +1040,14 @@ Channel (topic/event stream) definition for async APIs. "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [], - "response": [] - }, - "on_produce": [], - "on_consume": [] + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] } } } @@ -982,7 +1094,7 @@ Channel (topic/event stream) definition for async APIs. "name": "websocket-receiver", "type": "websocket" }, - "brokerDriver": { + "broker": { "name": "kafka-driver", "type": "kafka", "properties": { @@ -992,6 +1104,17 @@ Channel (topic/event stream) definition for async APIs. ] } }, + "allChannels": { + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + }, "channels": { "prices": { "produceTo": { @@ -1000,13 +1123,14 @@ Channel (topic/event stream) definition for async APIs. "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [], - "response": [] - }, - "on_produce": [], - "on_consume": [] + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] } } } @@ -1054,7 +1178,7 @@ and "type": "websocket", "properties": {} }, - "brokerDriver": { + "broker": { "name": "kafka-driver", "type": "kafka", "properties": { @@ -1064,17 +1188,19 @@ and ] } }, - "policies": { + "allChannels": { "on_connection_init": { - "request": [ + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "response": [ + ] + }, + "on_produce": { + "policies": [ { "name": "cors", "version": "v1", @@ -1083,22 +1209,16 @@ and } ] }, - "on_produce": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_consume": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] + "on_consume": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } }, "channels": { "property1": { @@ -1108,34 +1228,28 @@ and "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "response": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] - }, - "on_produce": [ + "on_connection_init": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_consume": [ + ] + }, + "on_produce": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_consume": { + "policies": [ { "name": "cors", "version": "v1", @@ -1152,34 +1266,28 @@ and "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "response": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] - }, - "on_produce": [ + "on_connection_init": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_consume": [ + ] + }, + "on_produce": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_consume": { + "policies": [ { "name": "cors", "version": "v1", @@ -1207,8 +1315,8 @@ and |version|string|true|none|Semantic version of the API| |context|string|true|none|Base path for all API routes (must start with /, no trailing slash)| |receiver|[WebBrokerApiReceiver](#schemawebbrokerapireceiver)|true|none|WebSocket receiver configuration| -|brokerDriver|[WebBrokerApiBrokerDriver](#schemawebbrokerapibrokerdriver)|true|none|Message broker driver configuration| -|policies|[WebBrokerApiPolicies](#schemawebbrokerapipolicies)|false|none|Protocol mediation policies applied at API level| +|broker|[WebBrokerApiBroker](#schemawebbrokerapibroker)|true|none|Message broker driver configuration| +|allChannels|[WebBrokerApiAllChannelPolicies](#schemawebbrokerapiallchannelpolicies)|false|none|Protocol mediation policies applied to all channels| |channels|object|true|none|Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name)| |» **additionalProperties**|[WebBrokerApiChannel](#schemawebbrokerapichannel)|false|none|WebSocket channel configuration with Kafka topic mapping| |vhosts|object|false|none|Custom virtual hosts/domains for the API| @@ -1249,12 +1357,12 @@ WebSocket receiver configuration |type|string|true|none|Receiver type| |properties|object|false|none|Additional receiver properties| -

WebBrokerApiBrokerDriver

+

WebBrokerApiBroker

- - - - + + + + ```json { @@ -1280,25 +1388,27 @@ Message broker driver configuration |type|string|true|none|Broker driver type| |properties|object|true|none|Broker driver properties (e.g., bootstrap servers)| -

WebBrokerApiPolicies

+

WebBrokerApiAllChannelPolicies

- - - - + + + + ```json { "on_connection_init": { - "request": [ + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "response": [ + ] + }, + "on_produce": { + "policies": [ { "name": "cors", "version": "v1", @@ -1307,54 +1417,40 @@ Message broker driver configuration } ] }, - "on_produce": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_consume": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] + "on_consume": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } } ``` -Protocol mediation policies applied at API level +Protocol mediation policies applied to all channels ### Properties |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|on_connection_init|[WebBrokerApiConnectionInitPolicies](#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| -|on_produce|[[Policy](#schemapolicy)]|false|none|Policies applied when client sends message to broker| -|on_consume|[[Policy](#schemapolicy)]|false|none|Policies applied when broker message delivered to client| +|on_connection_init|[WebBrokerApiPolicyGroup](#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|on_produce|[WebBrokerApiPolicyGroup](#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|on_consume|[WebBrokerApiPolicyGroup](#schemawebbrokerapipolicygroup)|false|none|Group of policies| -

WebBrokerApiConnectionInitPolicies

+

WebBrokerApiPolicyGroup

- - - - + + + + ```json { - "request": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "response": [ + "policies": [ { "name": "cors", "version": "v1", @@ -1366,14 +1462,13 @@ Protocol mediation policies applied at API level ``` -Connection initialization policies +Group of policies ### Properties |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|request|[[Policy](#schemapolicy)]|false|none|Policies applied before WebSocket upgrade| -|response|[[Policy](#schemapolicy)]|false|none|Policies applied after WebSocket upgrade| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies to apply|

WebBrokerApiChannel

@@ -1390,34 +1485,28 @@ Connection initialization policies "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "response": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] - }, - "on_produce": [ + "on_connection_init": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_consume": [ + ] + }, + "on_produce": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_consume": { + "policies": [ { "name": "cors", "version": "v1", @@ -1438,7 +1527,9 @@ WebSocket channel configuration with Kafka topic mapping |---|---|---|---|---| |produceTo|[WebBrokerApiProduceConfig](#schemawebbrokerapiproduceconfig)|false|none|Configuration for producing messages from WebSocket to Kafka| |consumeFrom|[WebBrokerApiConsumeConfig](#schemawebbrokerapiconsumeconfig)|false|none|Configuration for consuming messages from Kafka to WebSocket| -|policies|[WebBrokerApiChannelPolicies](#schemawebbrokerapichannelpolicies)|false|none|Channel-specific policies (override API-level policies)| +|on_connection_init|[WebBrokerApiPolicyGroup](#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|on_produce|[WebBrokerApiPolicyGroup](#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|on_consume|[WebBrokerApiPolicyGroup](#schemawebbrokerapipolicygroup)|false|none|Group of policies|

WebBrokerApiProduceConfig

@@ -1484,63 +1575,6 @@ Configuration for consuming messages from Kafka to WebSocket |---|---|---|---|---| |topic|string|true|none|Kafka topic to consume messages from| -

WebBrokerApiChannelPolicies

- - - - - - -```json -{ - "on_connection_init": { - "request": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "response": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] - }, - "on_produce": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_consume": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] -} - -``` - -Channel-specific policies (override API-level policies) - -### Properties - -|Name|Type|Required|Restrictions|Description| -|---|---|---|---|---| -|on_connection_init|[WebBrokerApiConnectionInitPolicies](#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| -|on_produce|[[Policy](#schemapolicy)]|false|none|Policies applied when client sends message to broker on this channel| -|on_consume|[[Policy](#schemapolicy)]|false|none|Policies applied when broker message delivered to client on this channel| -

APIKeyCreationRequest

diff --git a/docs/rest-apis/gateway/webbroker-api-management.md b/docs/rest-apis/gateway/webbroker-api-management.md index f63c97a58..ffbb4596d 100644 --- a/docs/rest-apis/gateway/webbroker-api-management.md +++ b/docs/rest-apis/gateway/webbroker-api-management.md @@ -37,7 +37,7 @@ Add a new WebBrokerAPI to the Gateway. WebBrokerAPI provides bidirectional strea "name": "websocket-receiver", "type": "websocket" }, - "brokerDriver": { + "broker": { "name": "kafka-driver", "type": "kafka", "properties": { @@ -47,6 +47,17 @@ Add a new WebBrokerAPI to the Gateway. WebBrokerAPI provides bidirectional strea ] } }, + "allChannels": { + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + }, "channels": { "prices": { "produceTo": { @@ -55,13 +66,14 @@ Add a new WebBrokerAPI to the Gateway. WebBrokerAPI provides bidirectional strea "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [], - "response": [] - }, - "on_produce": [], - "on_consume": [] + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] } } } @@ -103,7 +115,7 @@ Required roles: `admin`, `developer` "name": "websocket-receiver", "type": "websocket" }, - "brokerDriver": { + "broker": { "name": "kafka-driver", "type": "kafka", "properties": { @@ -113,6 +125,17 @@ Required roles: `admin`, `developer` ] } }, + "allChannels": { + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + }, "channels": { "prices": { "produceTo": { @@ -121,13 +144,14 @@ Required roles: `admin`, `developer` "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [], - "response": [] - }, - "on_produce": [], - "on_consume": [] + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] } } } @@ -216,7 +240,7 @@ Required roles: `admin`, `developer` "name": "websocket-receiver", "type": "websocket" }, - "brokerDriver": { + "broker": { "name": "kafka-driver", "type": "kafka", "properties": { @@ -226,6 +250,17 @@ Required roles: `admin`, `developer` ] } }, + "allChannels": { + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + }, "channels": { "prices": { "produceTo": { @@ -234,13 +269,14 @@ Required roles: `admin`, `developer` "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [], - "response": [] - }, - "on_produce": [], - "on_consume": [] + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] } } } @@ -295,30 +331,28 @@ Status Code **200** |»»»»» name|string|true|none|Receiver name| |»»»»» type|string|true|none|Receiver type| |»»»»» properties|object|false|none|Additional receiver properties| -|»»»» brokerDriver|[WebBrokerApiBrokerDriver](schemas.md#schemawebbrokerapibrokerdriver)|true|none|Message broker driver configuration| +|»»»» broker|[WebBrokerApiBroker](schemas.md#schemawebbrokerapibroker)|true|none|Message broker driver configuration| |»»»»» name|string|true|none|Broker driver name| |»»»»» type|string|true|none|Broker driver type| |»»»»» properties|object|true|none|Broker driver properties (e.g., bootstrap servers)| -|»»»» policies|[WebBrokerApiPolicies](schemas.md#schemawebbrokerapipolicies)|false|none|Protocol mediation policies applied at API level| -|»»»»» on_connection_init|[WebBrokerApiConnectionInitPolicies](schemas.md#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| -|»»»»»» request|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied before WebSocket upgrade| +|»»»» allChannels|[WebBrokerApiAllChannelPolicies](schemas.md#schemawebbrokerapiallchannelpolicies)|false|none|Protocol mediation policies applied to all channels| +|»»»»» on_connection_init|[WebBrokerApiPolicyGroup](schemas.md#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|»»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies to apply| |»»»»»»» name|string|true|none|Name of the policy| |»»»»»»» version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected. The Gateway Controller resolves the major version to the single matching full version installed in the gateway image.| |»»»»»»» executionCondition|string|false|none|Expression controlling conditional execution of the policy| |»»»»»»» params|object|false|none|Arbitrary parameters for the policy (free-form key/value structure)| -|»»»»»» response|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied after WebSocket upgrade| -|»»»»» on_produce|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when client sends message to broker| -|»»»»» on_consume|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when broker message delivered to client| +|»»»»» on_produce|[WebBrokerApiPolicyGroup](schemas.md#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|»»»»» on_consume|[WebBrokerApiPolicyGroup](schemas.md#schemawebbrokerapipolicygroup)|false|none|Group of policies| |»»»» channels|object|true|none|Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name)| |»»»»» **additionalProperties**|[WebBrokerApiChannel](schemas.md#schemawebbrokerapichannel)|false|none|WebSocket channel configuration with Kafka topic mapping| |»»»»»» produceTo|[WebBrokerApiProduceConfig](schemas.md#schemawebbrokerapiproduceconfig)|false|none|Configuration for producing messages from WebSocket to Kafka| |»»»»»»» topic|string|true|none|Kafka topic to produce messages to| |»»»»»» consumeFrom|[WebBrokerApiConsumeConfig](schemas.md#schemawebbrokerapiconsumeconfig)|false|none|Configuration for consuming messages from Kafka to WebSocket| |»»»»»»» topic|string|true|none|Kafka topic to consume messages from| -|»»»»»» policies|[WebBrokerApiChannelPolicies](schemas.md#schemawebbrokerapichannelpolicies)|false|none|Channel-specific policies (override API-level policies)| -|»»»»»»» on_connection_init|[WebBrokerApiConnectionInitPolicies](schemas.md#schemawebbrokerapiconnectioninitpolicies)|false|none|Connection initialization policies| -|»»»»»»» on_produce|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when client sends message to broker on this channel| -|»»»»»»» on_consume|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when broker message delivered to client on this channel| +|»»»»»» on_connection_init|[WebBrokerApiPolicyGroup](schemas.md#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|»»»»»» on_produce|[WebBrokerApiPolicyGroup](schemas.md#schemawebbrokerapipolicygroup)|false|none|Group of policies| +|»»»»»» on_consume|[WebBrokerApiPolicyGroup](schemas.md#schemawebbrokerapipolicygroup)|false|none|Group of policies| |»»»» vhosts|object|false|none|Custom virtual hosts/domains for the API| |»»»»» main|string|true|none|Custom virtual host/domain for production traffic| |»»»»» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| @@ -403,7 +437,7 @@ Required roles: `admin`, `developer` "name": "websocket-receiver", "type": "websocket" }, - "brokerDriver": { + "broker": { "name": "kafka-driver", "type": "kafka", "properties": { @@ -413,6 +447,17 @@ Required roles: `admin`, `developer` ] } }, + "allChannels": { + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + }, "channels": { "prices": { "produceTo": { @@ -421,13 +466,14 @@ Required roles: `admin`, `developer` "consumeFrom": { "topic": "stock.prices" }, - "policies": { - "on_connection_init": { - "request": [], - "response": [] - }, - "on_produce": [], - "on_consume": [] + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] } } } diff --git a/docs/rest-apis/gateway/websub-api-management.md b/docs/rest-apis/gateway/websub-api-management.md index 7aac15351..d1a227e0a 100644 --- a/docs/rest-apis/gateway/websub-api-management.md +++ b/docs/rest-apis/gateway/websub-api-management.md @@ -229,19 +229,22 @@ Status Code **200** |»»»» vhosts|object|false|none|Custom virtual hosts/domains for the API| |»»»»» main|string|true|none|Custom virtual host/domain for production traffic| |»»»»» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| -|»»»» receiver|[WebSubReceiver](schemas.md#schemawebsubreceiver)|false|none|Receiver configuration for the WebSub API - handles inbound event publishing from publishers.| -|»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied to inbound webhook requests (e.g., hmac-signature-validation)| +|»»»» policies|[WebSubAllChannelPolicies](schemas.md#schemawebsuballchannelpolicies)|false|none|Policies applied to all channels, organized by event type.| +|»»»»» on_subscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client subscribes to a channel (e.g., api-key-auth)| |»»»»»» name|string|true|none|Name of the policy| |»»»»»» version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected. The Gateway Controller resolves the major version to the single matching full version installed in the gateway image.| |»»»»»» executionCondition|string|false|none|Expression controlling conditional execution of the policy| |»»»»»» params|object|false|none|Arbitrary parameters for the policy (free-form key/value structure)| -|»»»» hub|[WebSubHub](schemas.md#schemawebsubhub)|true|none|Hub configuration for the WebSub API - handles subscriber management and event fan-out.| -|»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied at the hub level (e.g., api-key-auth for subscribers)| -|»»»»» channels|[[HubChannel](schemas.md#schemahubchannel)]|true|none|List of topic channels available for subscription| -|»»»»»» name|string|true|none|Channel name or topic identifier relative to API context.| -|»»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied only to this channel (e.g., rbac)| -|»»»» delivery|[WebSubDelivery](schemas.md#schemawebsubdelivery)|false|none|Delivery configuration for the WebSub API - handles outbound event delivery to subscribers.| -|»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied when delivering events to subscriber callback URLs (e.g., hmac-signature-validation)| +|»»»»» on_unsubscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client unsubscribes from a channel (e.g., api-key-auth)| +|»»»»» on_message_received|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a message is received from the publisher via webhook (e.g., hmac-signature-validation)| +|»»»»» on_message_delivery|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when delivering a message to a subscriber callback URL (e.g., hmac-sign-messages)| +|»»»» channels|object|false|none|Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel.| +|»»»»» **additionalProperties**|[WebSubChannel](schemas.md#schemawebsubchannel)|false|none|A single channel definition with optional per-channel policy overrides.| +|»»»»»» policies|[WebSubChannelPolicies](schemas.md#schemawebsubchannelpolicies)|false|none|Policies applied to a specific channel, organized by event type.| +|»»»»»»» on_subscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client subscribes to this channel (e.g., rbac)| +|»»»»»»» on_unsubscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client unsubscribes from this channel| +|»»»»»»» on_message_received|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a message is received for this channel| +|»»»»»»» on_message_delivery|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when delivering a message for this channel| |»»»» deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment.| *and* diff --git a/event-gateway/gateway-runtime/internal/xdsclient/handler.go b/event-gateway/gateway-runtime/internal/xdsclient/handler.go index 45f033720..404578bdb 100644 --- a/event-gateway/gateway-runtime/internal/xdsclient/handler.go +++ b/event-gateway/gateway-runtime/internal/xdsclient/handler.go @@ -422,14 +422,10 @@ func (h *Handler) toWebBrokerApiBinding(ecr EventChannelResource) binding.WebBro var apiPolicies binding.ProtocolMediationPolicies if ecr.Policies != nil { if policiesMap, ok := ecr.Policies.(map[string]interface{}); ok { - // Parse on_connection_init - if connInitIface, ok := policiesMap["on_connection_init"].(map[string]interface{}); ok { - if reqIface, ok := connInitIface["request"].([]interface{}); ok { - apiPolicies.OnConnectionInit.Request = mapGenericPolicyList(reqIface) - } - if respIface, ok := connInitIface["response"].([]interface{}); ok { - apiPolicies.OnConnectionInit.Response = mapGenericPolicyList(respIface) - } + // Parse on_connection_init (now a flat array) + if connInitIface, ok := policiesMap["on_connection_init"].([]interface{}); ok { + // Put all policies in Request for backward compatibility with execution logic + apiPolicies.OnConnectionInit.Request = mapGenericPolicyList(connInitIface) } // Parse on_produce if produceIface, ok := policiesMap["on_produce"].([]interface{}); ok { @@ -466,14 +462,10 @@ func (h *Handler) toWebBrokerApiBinding(ecr EventChannelResource) binding.WebBro // Parse policies from nested "policies" field if policiesIface, ok := channelData["policies"].(map[string]interface{}); ok { - // Parse channel on_connection_init - if connInitIface, ok := policiesIface["on_connection_init"].(map[string]interface{}); ok { - if reqIface, ok := connInitIface["request"].([]interface{}); ok { - channelDef.OnConnectionInit.Request = mapGenericPolicyList(reqIface) - } - if respIface, ok := connInitIface["response"].([]interface{}); ok { - channelDef.OnConnectionInit.Response = mapGenericPolicyList(respIface) - } + // Parse channel on_connection_init (now a flat array) + if connInitIface, ok := policiesIface["on_connection_init"].([]interface{}); ok { + // Put all policies in Request for backward compatibility with execution logic + channelDef.OnConnectionInit.Request = mapGenericPolicyList(connInitIface) } // Parse channel on_produce if produceIface, ok := policiesIface["on_produce"].([]interface{}); ok { diff --git a/gateway/gateway-controller/api/management-openapi.yaml b/gateway/gateway-controller/api/management-openapi.yaml index 9b0b0efea..ae0a810ba 100644 --- a/gateway/gateway-controller/api/management-openapi.yaml +++ b/gateway/gateway-controller/api/management-openapi.yaml @@ -4122,25 +4122,32 @@ components: receiver: name: websocket-receiver type: websocket - brokerDriver: + broker: name: kafka-driver type: kafka properties: bootstrapServers: - kafka-broker-1:9092 - kafka-broker-2:9092 + allChannels: + on_connection_init: + policies: [] + on_produce: + policies: [] + on_consume: + policies: [] channels: prices: produceTo: topic: stock.prices consumeFrom: topic: stock.prices - policies: - on_connection_init: - request: [] - response: [] - on_produce: [] - on_consume: [] + on_connection_init: + policies: [] + on_produce: + policies: [] + on_consume: + policies: [] WebBrokerApi: allOf: @@ -4164,25 +4171,32 @@ components: receiver: name: websocket-receiver type: websocket - brokerDriver: + broker: name: kafka-driver type: kafka properties: bootstrapServers: - kafka-broker-1:9092 - kafka-broker-2:9092 + allChannels: + on_connection_init: + policies: [] + on_produce: + policies: [] + on_consume: + policies: [] channels: prices: produceTo: topic: stock.prices consumeFrom: topic: stock.prices - policies: - on_connection_init: - request: [] - response: [] - on_produce: [] - on_consume: [] + on_connection_init: + policies: [] + on_produce: + policies: [] + on_consume: + policies: [] status: id: stock-trading-v1.0 state: deployed @@ -4197,7 +4211,7 @@ components: - version - context - receiver - - brokerDriver + - broker - channels properties: displayName: @@ -4221,10 +4235,10 @@ components: example: /stock-trading receiver: $ref: '#/components/schemas/WebBrokerApiReceiver' - brokerDriver: - $ref: '#/components/schemas/WebBrokerApiBrokerDriver' - policies: - $ref: '#/components/schemas/WebBrokerApiPolicies' + broker: + $ref: '#/components/schemas/WebBrokerApiBroker' + allChannels: + $ref: '#/components/schemas/WebBrokerApiAllChannelPolicies' channels: type: object description: Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name) @@ -4274,7 +4288,7 @@ components: description: Additional receiver properties additionalProperties: true - WebBrokerApiBrokerDriver: + WebBrokerApiBroker: type: object description: Message broker driver configuration required: @@ -4299,35 +4313,24 @@ components: - kafka-broker-1:9092 - kafka-broker-2:9092 - WebBrokerApiPolicies: + WebBrokerApiAllChannelPolicies: type: object - description: Protocol mediation policies applied at API level + description: Protocol mediation policies applied to all channels properties: on_connection_init: - $ref: '#/components/schemas/WebBrokerApiConnectionInitPolicies' + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' on_produce: - type: array - description: Policies applied when client sends message to broker - items: - $ref: '#/components/schemas/Policy' + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' on_consume: - type: array - description: Policies applied when broker message delivered to client - items: - $ref: '#/components/schemas/Policy' + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' - WebBrokerApiConnectionInitPolicies: + WebBrokerApiPolicyGroup: type: object - description: Connection initialization policies + description: Group of policies properties: - request: - type: array - description: Policies applied before WebSocket upgrade - items: - $ref: '#/components/schemas/Policy' - response: + policies: type: array - description: Policies applied after WebSocket upgrade + description: List of policies to apply items: $ref: '#/components/schemas/Policy' @@ -4339,8 +4342,12 @@ components: $ref: '#/components/schemas/WebBrokerApiProduceConfig' consumeFrom: $ref: '#/components/schemas/WebBrokerApiConsumeConfig' - policies: - $ref: '#/components/schemas/WebBrokerApiChannelPolicies' + on_connection_init: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + on_produce: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + on_consume: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' WebBrokerApiProduceConfig: type: object @@ -4364,22 +4371,7 @@ components: description: Kafka topic to consume messages from example: stock.prices - WebBrokerApiChannelPolicies: - type: object - description: Channel-specific policies (override API-level policies) - properties: - on_connection_init: - $ref: '#/components/schemas/WebBrokerApiConnectionInitPolicies' - on_produce: - type: array - description: Policies applied when client sends message to broker on this channel - items: - $ref: '#/components/schemas/Policy' - on_consume: - type: array - description: Policies applied when broker message delivered to client on this channel - items: - $ref: '#/components/schemas/Policy' + APIKeyCreationRequest: type: object diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index 7f1e505c9..fce59f96b 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -1597,8 +1597,20 @@ type WebBrokerApiApiVersion string // WebBrokerApiKind API type type WebBrokerApiKind string -// WebBrokerApiBrokerDriver Message broker driver configuration -type WebBrokerApiBrokerDriver struct { +// WebBrokerApiAllChannelPolicies Protocol mediation policies applied to all channels +type WebBrokerApiAllChannelPolicies struct { + // OnConnectionInit Group of policies + OnConnectionInit *WebBrokerApiPolicyGroup `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` + + // OnConsume Group of policies + OnConsume *WebBrokerApiPolicyGroup `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` + + // OnProduce Group of policies + OnProduce *WebBrokerApiPolicyGroup `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` +} + +// WebBrokerApiBroker Message broker driver configuration +type WebBrokerApiBroker struct { // Name Broker driver name Name string `json:"name" yaml:"name"` @@ -1614,32 +1626,17 @@ type WebBrokerApiChannel struct { // ConsumeFrom Configuration for consuming messages from Kafka to WebSocket ConsumeFrom *WebBrokerApiConsumeConfig `json:"consumeFrom,omitempty" yaml:"consumeFrom,omitempty"` - // Policies Channel-specific policies (override API-level policies) - Policies *WebBrokerApiChannelPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` - - // ProduceTo Configuration for producing messages from WebSocket to Kafka - ProduceTo *WebBrokerApiProduceConfig `json:"produceTo,omitempty" yaml:"produceTo,omitempty"` -} - -// WebBrokerApiChannelPolicies Channel-specific policies (override API-level policies) -type WebBrokerApiChannelPolicies struct { - // OnConnectionInit Connection initialization policies - OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` + // OnConnectionInit Group of policies + OnConnectionInit *WebBrokerApiPolicyGroup `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` - // OnConsume Policies applied when broker message delivered to client on this channel - OnConsume *[]Policy `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` + // OnConsume Group of policies + OnConsume *WebBrokerApiPolicyGroup `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` - // OnProduce Policies applied when client sends message to broker on this channel - OnProduce *[]Policy `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` -} - -// WebBrokerApiConnectionInitPolicies Connection initialization policies -type WebBrokerApiConnectionInitPolicies struct { - // Request Policies applied before WebSocket upgrade - Request *[]Policy `json:"request,omitempty" yaml:"request,omitempty"` + // OnProduce Group of policies + OnProduce *WebBrokerApiPolicyGroup `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` - // Response Policies applied after WebSocket upgrade - Response *[]Policy `json:"response,omitempty" yaml:"response,omitempty"` + // ProduceTo Configuration for producing messages from WebSocket to Kafka + ProduceTo *WebBrokerApiProduceConfig `json:"produceTo,omitempty" yaml:"produceTo,omitempty"` } // WebBrokerApiConsumeConfig Configuration for consuming messages from Kafka to WebSocket @@ -1650,8 +1647,11 @@ type WebBrokerApiConsumeConfig struct { // WebBrokerApiData defines model for WebBrokerApiData. type WebBrokerApiData struct { - // BrokerDriver Message broker driver configuration - BrokerDriver WebBrokerApiBrokerDriver `json:"brokerDriver" yaml:"brokerDriver"` + // AllChannels Protocol mediation policies applied to all channels + AllChannels *WebBrokerApiAllChannelPolicies `json:"allChannels,omitempty" yaml:"allChannels,omitempty"` + + // Broker Message broker driver configuration + Broker WebBrokerApiBroker `json:"broker" yaml:"broker"` // Channels Map of WebSocket channels for bidirectional streaming with Kafka (key is channel name) Channels map[string]WebBrokerApiChannel `json:"channels" yaml:"channels"` @@ -1665,9 +1665,6 @@ type WebBrokerApiData struct { // DisplayName Human-readable API name (must be URL-friendly - only letters, numbers, spaces, hyphens, underscores, and dots allowed) DisplayName string `json:"displayName" yaml:"displayName"` - // Policies Protocol mediation policies applied at API level - Policies *WebBrokerApiPolicies `json:"policies,omitempty" yaml:"policies,omitempty"` - // Receiver WebSocket receiver configuration Receiver WebBrokerApiReceiver `json:"receiver" yaml:"receiver"` @@ -1687,16 +1684,10 @@ type WebBrokerApiData struct { // WebBrokerApiDataDeploymentState Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration and policies are preserved for potential redeployment. type WebBrokerApiDataDeploymentState string -// WebBrokerApiPolicies Protocol mediation policies applied at API level -type WebBrokerApiPolicies struct { - // OnConnectionInit Connection initialization policies - OnConnectionInit *WebBrokerApiConnectionInitPolicies `json:"on_connection_init,omitempty" yaml:"on_connection_init,omitempty"` - - // OnConsume Policies applied when broker message delivered to client - OnConsume *[]Policy `json:"on_consume,omitempty" yaml:"on_consume,omitempty"` - - // OnProduce Policies applied when client sends message to broker - OnProduce *[]Policy `json:"on_produce,omitempty" yaml:"on_produce,omitempty"` +// WebBrokerApiPolicyGroup Group of policies +type WebBrokerApiPolicyGroup struct { + // Policies List of policies to apply + Policies *[]Policy `json:"policies,omitempty" yaml:"policies,omitempty"` } // WebBrokerApiProduceConfig Configuration for producing messages from WebSocket to Kafka @@ -4826,261 +4817,260 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "EyMCAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go index 368ad37a4..cb72718b1 100644 --- a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go +++ b/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go @@ -182,36 +182,30 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke // Build broker-driver configuration brokerDriver := map[string]interface{}{ - "name": spec.BrokerDriver.Name, - "type": spec.BrokerDriver.Type, - "properties": spec.BrokerDriver.Properties, + "name": spec.Broker.Name, + "type": spec.Broker.Type, + "properties": spec.Broker.Properties, } slog.Info("DEBUG: Building EventChannelConfig for WebBrokerApi", "name", webBrokerCfg.Metadata.Name, "receiverName", spec.Receiver.Name, - "brokerDriverName", spec.BrokerDriver.Name, - "brokerDriverType", spec.BrokerDriver.Type) + "brokerDriverName", spec.Broker.Name, + "brokerDriverType", spec.Broker.Type) - // Build API-level policies - var apiOnConnectionInitRequest []interface{} - var apiOnConnectionInitResponse []interface{} + // Build API-level policies from AllChannels + var apiOnConnectionInit []interface{} var apiOnProduce []interface{} var apiOnConsume []interface{} - if spec.Policies != nil { - if spec.Policies.OnConnectionInit != nil { - if spec.Policies.OnConnectionInit.Request != nil { - apiOnConnectionInitRequest = buildPolicyList(spec.Policies.OnConnectionInit.Request) - } - if spec.Policies.OnConnectionInit.Response != nil { - apiOnConnectionInitResponse = buildPolicyList(spec.Policies.OnConnectionInit.Response) - } + if spec.AllChannels != nil { + if spec.AllChannels.OnConnectionInit != nil && spec.AllChannels.OnConnectionInit.Policies != nil { + apiOnConnectionInit = buildPolicyList(spec.AllChannels.OnConnectionInit.Policies) } - if spec.Policies.OnProduce != nil { - apiOnProduce = buildPolicyList(spec.Policies.OnProduce) + if spec.AllChannels.OnProduce != nil && spec.AllChannels.OnProduce.Policies != nil { + apiOnProduce = buildPolicyList(spec.AllChannels.OnProduce.Policies) } - if spec.Policies.OnConsume != nil { - apiOnConsume = buildPolicyList(spec.Policies.OnConsume) + if spec.AllChannels.OnConsume != nil && spec.AllChannels.OnConsume.Policies != nil { + apiOnConsume = buildPolicyList(spec.AllChannels.OnConsume.Policies) } } @@ -219,26 +213,19 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke channels := make(map[string]interface{}) if spec.Channels != nil { for channelName, channelConfig := range spec.Channels { - var channelOnConnectionInitRequest []interface{} - var channelOnConnectionInitResponse []interface{} + var channelOnConnectionInit []interface{} var channelOnProduce []interface{} var channelOnConsume []interface{} - if channelConfig.Policies != nil { - if channelConfig.Policies.OnConnectionInit != nil { - if channelConfig.Policies.OnConnectionInit.Request != nil { - channelOnConnectionInitRequest = buildPolicyList(channelConfig.Policies.OnConnectionInit.Request) - } - if channelConfig.Policies.OnConnectionInit.Response != nil { - channelOnConnectionInitResponse = buildPolicyList(channelConfig.Policies.OnConnectionInit.Response) - } - } - if channelConfig.Policies.OnProduce != nil { - channelOnProduce = buildPolicyList(channelConfig.Policies.OnProduce) - } - if channelConfig.Policies.OnConsume != nil { - channelOnConsume = buildPolicyList(channelConfig.Policies.OnConsume) - } + // Extract policies from channel-level policy groups + if channelConfig.OnConnectionInit != nil && channelConfig.OnConnectionInit.Policies != nil { + channelOnConnectionInit = buildPolicyList(channelConfig.OnConnectionInit.Policies) + } + if channelConfig.OnProduce != nil && channelConfig.OnProduce.Policies != nil { + channelOnProduce = buildPolicyList(channelConfig.OnProduce.Policies) + } + if channelConfig.OnConsume != nil && channelConfig.OnConsume.Policies != nil { + channelOnConsume = buildPolicyList(channelConfig.OnConsume.Policies) } // Build channel entry with policies nested inside "policies" field @@ -258,14 +245,11 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke } } - // Nest all policies inside a "policies" field + // Nest all policies inside a "policies" field (flattened structure) channelEntry["policies"] = map[string]interface{}{ - "on_connection_init": map[string]interface{}{ - "request": channelOnConnectionInitRequest, - "response": channelOnConnectionInitResponse, - }, - "on_produce": channelOnProduce, - "on_consume": channelOnConsume, + "on_connection_init": channelOnConnectionInit, + "on_produce": channelOnProduce, + "on_consume": channelOnConsume, } channels[channelName] = channelEntry @@ -281,12 +265,9 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke "receiver": receiver, "broker-driver": brokerDriver, "policies": map[string]interface{}{ - "on_connection_init": map[string]interface{}{ - "request": apiOnConnectionInitRequest, - "response": apiOnConnectionInitResponse, - }, - "on_produce": apiOnProduce, - "on_consume": apiOnConsume, + "on_connection_init": apiOnConnectionInit, + "on_produce": apiOnProduce, + "on_consume": apiOnConsume, }, "channels": channels, } From fc0ea91e34e277b00b2a42658cd030036f636ae3 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 18:11:25 +0530 Subject: [PATCH 12/20] Update Kafka broker URL examples --- docs/rest-apis/gateway/schemas.md | 8 +- .../gateway/webbroker-api-management.md | 8 +- .../channels-webbrokerapi-example.yaml | 9 +- .../gateway-runtime/configs/channels.yaml | 3 +- .../api/management-openapi.yaml | 10 +- .../pkg/api/management/generated.go | 238 +++++++++--------- 6 files changed, 141 insertions(+), 135 deletions(-) diff --git a/docs/rest-apis/gateway/schemas.md b/docs/rest-apis/gateway/schemas.md index f651e636c..01f4e7bf4 100644 --- a/docs/rest-apis/gateway/schemas.md +++ b/docs/rest-apis/gateway/schemas.md @@ -1015,7 +1015,7 @@ Channel (topic/event stream) definition for async APIs. "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] @@ -1098,7 +1098,7 @@ Channel (topic/event stream) definition for async APIs. "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] @@ -1182,7 +1182,7 @@ and "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] @@ -1369,7 +1369,7 @@ WebSocket receiver configuration "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] diff --git a/docs/rest-apis/gateway/webbroker-api-management.md b/docs/rest-apis/gateway/webbroker-api-management.md index ffbb4596d..3fac511e7 100644 --- a/docs/rest-apis/gateway/webbroker-api-management.md +++ b/docs/rest-apis/gateway/webbroker-api-management.md @@ -41,7 +41,7 @@ Add a new WebBrokerAPI to the Gateway. WebBrokerAPI provides bidirectional strea "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] @@ -119,7 +119,7 @@ Required roles: `admin`, `developer` "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] @@ -244,7 +244,7 @@ Required roles: `admin`, `developer` "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] @@ -441,7 +441,7 @@ Required roles: `admin`, `developer` "name": "kafka-driver", "type": "kafka", "properties": { - "bootstrapServers": [ + "brokers": [ "kafka-broker-1:9092", "kafka-broker-2:9092" ] diff --git a/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml b/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml index 30a1bd280..f8599845e 100644 --- a/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml +++ b/event-gateway/gateway-runtime/configs/channels-webbrokerapi-example.yaml @@ -14,8 +14,13 @@ channels: broker-driver: type: kafka properties: - bootstrap.servers: localhost:9092 - security.protocol: PLAINTEXT + brokers: + - localhost:9092 + # Other optional properties: + # tls: false + # sasl_mechanism: plain + # sasl_username: user + # sasl_password: pass # Channel definitions with topic mappings channels: issues: diff --git a/event-gateway/gateway-runtime/configs/channels.yaml b/event-gateway/gateway-runtime/configs/channels.yaml index 609c19b19..6bb8d2fdb 100644 --- a/event-gateway/gateway-runtime/configs/channels.yaml +++ b/event-gateway/gateway-runtime/configs/channels.yaml @@ -32,7 +32,8 @@ channels: type: kafka properties: topic: repo-events - bootstrap.servers: kafka:29092 + brokers: + - kafka:29092 allChannelPolicies: on_connection_init: request: [] diff --git a/gateway/gateway-controller/api/management-openapi.yaml b/gateway/gateway-controller/api/management-openapi.yaml index ae0a810ba..c6618e886 100644 --- a/gateway/gateway-controller/api/management-openapi.yaml +++ b/gateway/gateway-controller/api/management-openapi.yaml @@ -4126,7 +4126,7 @@ components: name: kafka-driver type: kafka properties: - bootstrapServers: + brokers: - kafka-broker-1:9092 - kafka-broker-2:9092 allChannels: @@ -4175,7 +4175,7 @@ components: name: kafka-driver type: kafka properties: - bootstrapServers: + brokers: - kafka-broker-1:9092 - kafka-broker-2:9092 allChannels: @@ -4309,9 +4309,9 @@ components: description: Broker driver properties (e.g., bootstrap servers) additionalProperties: true example: - bootstrapServers: - - kafka-broker-1:9092 - - kafka-broker-2:9092 + brokers: + - kafka-broker-1:9092 + - kafka-broker-2:9092 WebBrokerApiAllChannelPolicies: type: object diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index fce59f96b..3bc65909e 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -4952,125 +4952,125 @@ var swaggerSpec = []string{ "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", - "EyMCAA==", + "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", + "IgIA", } // GetSwagger returns the content of the embedded swagger specification file From 26ace86f97d942e3d3cdd5ff255ea0b3674041c4 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 18:29:55 +0530 Subject: [PATCH 13/20] Cleanup unwanted TODOs --- .../connectors/brokerdriver/kafka/consumer.go | 3 +-- .../websocket/broker_api_connector.go | 24 +++++++------------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go b/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go index 59ff89540..0881bd7c5 100644 --- a/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go +++ b/event-gateway/gateway-runtime/internal/connectors/brokerdriver/kafka/consumer.go @@ -94,8 +94,7 @@ func (c *Consumer) consumeLoop(ctx context.Context) { } fetches.EachRecord(func(record *kgo.Record) { - // TODO: Change to debug level before production deployment - slog.Info("[7] Message read from Kafka", + slog.Debug("[7] Message read from Kafka", "topic", record.Topic, "partition", record.Partition, "offset", record.Offset, diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go index 9e46259eb..df50b360e 100644 --- a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -139,8 +139,6 @@ func (e *WebBrokerApiReceiver) Stop(ctx context.Context) error { // handleUpgrade handles WebSocket upgrade requests. func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Request) { - // TODO: Change detailed flow logs ([1]-[8]) to debug level before production deployment - // These Info-level logs are useful for development/testing but should be Debug in production // Extract channel name from X-topic header. xTopicHeader := r.Header.Get("X-topic") channelName := xTopicHeader @@ -179,7 +177,7 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ return } - slog.Info("[1] WebSocket connection attempted", + slog.Debug("[1] WebSocket connection attempted", "api", e.channel.Name, "channel", channelName, "method", r.Method, @@ -189,7 +187,7 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ "connection_header", r.Header.Get("Connection")) // Apply API-level on_connection_init.request policies. - slog.Info("[2] Applying API-level onConnectionInit.request policies", + slog.Debug("[2] Applying API-level onConnectionInit.request policies", "api", e.channel.Name, "channel", channelName, "remote_addr", r.RemoteAddr) @@ -233,10 +231,6 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ } } - // Apply channel-specific on_connection_init.request policies. - // TODO: Implement channel-specific policy application here. - // For now, only API-level policies are applied. - // Upgrade to WebSocket. ws, err := upgrader.Upgrade(w, r, nil) if err != nil { @@ -245,7 +239,7 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ } // Apply API-level on_connection_init.response policies. - slog.Info("[3] Applying API-level onConnectionInit.response policies", "api", e.channel.Name, "channel", channelName) + slog.Debug("[3] Applying API-level onConnectionInit.response policies", "api", e.channel.Name, "channel", channelName) respMsg := &connectors.Message{ Headers: map[string][]string{}, @@ -349,7 +343,7 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ e.connections[connID] = conn e.mu.Unlock() - slog.Info("[4] WebSocket handshake completed", "connID", connID, "api", e.channel.Name, "channel", channelName, "remote", ws.RemoteAddr(), "consumer_group", groupID, "topics", channelTopics) + slog.Debug("[4] WebSocket handshake completed", "connID", connID, "api", e.channel.Name, "channel", channelName, "remote", ws.RemoteAddr(), "consumer_group", groupID, "topics", channelTopics) // Start goroutines for bidirectional communication. go e.inboundLoop(ctx, conn) @@ -380,7 +374,7 @@ func (e *WebBrokerApiReceiver) readLoop(ctx context.Context, conn *brokerApiConn continue } - slog.Info("[5] Message received from WebSocket client", + slog.Debug("[5] Message received from WebSocket client", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName, @@ -411,7 +405,7 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC return case msg := <-conn.inbound: // Apply channel-specific on_produce policies. - slog.Info("[5] Applying channel onProduce policies", + slog.Debug("[5] Applying channel onProduce policies", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName, @@ -446,7 +440,7 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC } // Publish to Kafka. - slog.Info("[6] Publishing message to Kafka", + slog.Debug("[6] Publishing message to Kafka", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName, @@ -469,7 +463,7 @@ func (e *WebBrokerApiReceiver) outboundLoop(ctx context.Context, conn *brokerApi case <-ctx.Done(): return case msg := <-conn.outbound: - slog.Info("[7] Applying channel onConsume policies", + slog.Debug("[7] Applying channel onConsume policies", "connID", conn.connID, "api", e.channel.Name, "channel", conn.channelName, @@ -487,7 +481,7 @@ func (e *WebBrokerApiReceiver) outboundLoop(ctx context.Context, conn *brokerApi continue } - slog.Info("[8] Sending message to WebSocket client", + slog.Debug("[8] Sending message to WebSocket client", "connID", conn.connID, "channel", e.channel.Name, "message_size", len(processed.Value)) From e4d7316ced66fdfea3b7d5e1801c441eae449d73 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 18:56:12 +0530 Subject: [PATCH 14/20] Update config files --- event-gateway/docker-compose.yaml | 6 +- .../gateway-runtime/configs/channels.yaml | 71 +++++++++++++++---- .../gateway-runtime/configs/config.toml | 3 +- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/event-gateway/docker-compose.yaml b/event-gateway/docker-compose.yaml index 18f355488..d5c3fd9e7 100644 --- a/event-gateway/docker-compose.yaml +++ b/event-gateway/docker-compose.yaml @@ -57,7 +57,7 @@ services: - APIP_EGW_SERVER_WEBSUB_ENABLED=true - APIP_EGW_SERVER_WEBSUB_HTTP_PORT=8080 - APIP_EGW_SERVER_WEBSUB_HTTPS_PORT=8443 - - APIP_EGW_SERVER_WEBSUB_TLS_ENABLED=false # TODO TEST WITH TLS + - APIP_EGW_SERVER_WEBSUB_TLS_ENABLED=true - APIP_EGW_SERVER_WEBSUB_TLS_CERT_FILE=/etc/event-gateway/tls/default-listener.crt - APIP_EGW_SERVER_WEBSUB_TLS_KEY_FILE=/etc/event-gateway/tls/default-listener.key - APIP_EGW_SERVER_WEBSOCKET_PORT=8081 @@ -69,8 +69,8 @@ services: - "host.docker.internal:host-gateway" volumes: - ./listener-certs:/etc/event-gateway/tls:ro - - ./gateway-runtime/configs/config.toml:/etc/event-gateway/config.toml:ro - - ./gateway-runtime/configs/channels.yaml:/etc/event-gateway/channels.yaml:ro + - ./configs/event-gateway/config.toml:/etc/event-gateway/config.toml:ro + - ./configs/event-gateway/channels.yaml:/etc/event-gateway/channels.yaml:ro depends_on: gateway-controller: condition: service_started diff --git a/event-gateway/gateway-runtime/configs/channels.yaml b/event-gateway/gateway-runtime/configs/channels.yaml index 6bb8d2fdb..bab73c215 100644 --- a/event-gateway/gateway-runtime/configs/channels.yaml +++ b/event-gateway/gateway-runtime/configs/channels.yaml @@ -20,23 +20,66 @@ # outbound: Applied when an event is delivered to a subscriber callback (data delivery). channels: - - kind: WebBrokerApi - apiId: websocket-kafka-api-v1-0 - name: websocket-kafka-api - version: v1.0 - context: /ws-kafka + # WebSubApi example: repo-watcher with multiple channels + - kind: WebSubApi + name: repo-watcher + version: v1 + context: /repos + channels: + - name: issues + - name: pull-requests + - name: commits + receiver: + type: websub + broker-driver: + type: kafka + config: + brokers: + - kafka:29092 + policies: + subscribe: + - name: basic-auth + version: v1 + params: + username: "admin" + password: "admin" + inbound: [] + outbound: [] + + # Protocol Mediation example (WS → Kafka, 1:1 passthrough) + - name: live-prices + mode: protocol-mediation + context: /prices + version: v1 receiver: type: websocket - properties: {} + path: /stream broker-driver: type: kafka - properties: - topic: repo-events + topic: price-updates + config: brokers: - kafka:29092 - allChannelPolicies: - on_connection_init: - request: [] - response: [] - on_produce: [] - on_consume: [] + policies: + subscribe: [] + inbound: [] + outbound: [] + + # WebSubApi with single channel + # - kind: WebSubApi + # name: order-events + # version: v1 + # context: /orders + # channels: + # - name: orders + # receiver: + # type: websub + # broker-driver: + # type: kafka + # config: + # brokers: + # - kafka:29092 + # policies: + # subscribe: [] + # inbound: [] + # outbound: [] diff --git a/event-gateway/gateway-runtime/configs/config.toml b/event-gateway/gateway-runtime/configs/config.toml index 44813d9e2..687b18b53 100644 --- a/event-gateway/gateway-runtime/configs/config.toml +++ b/event-gateway/gateway-runtime/configs/config.toml @@ -15,8 +15,7 @@ websub_http_port = 8080 # HTTPS port for WebSub server (used when TLS is enabled) websub_https_port = 8443 # Set to true to serve the WebSub hub and webhook receiver over HTTPS. -# TODO: Test WebBrokerApi with TLS enabled (requires generating certificates in listener-certs/) -websub_tls_enabled = false +websub_tls_enabled = true websub_tls_cert_file = "/etc/event-gateway/tls/default-listener.crt" websub_tls_key_file = "/etc/event-gateway/tls/default-listener.key" websocket_port = 8081 From 7e39a0045b2f13b7cecf57bc0278b6f66a3509b6 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 19:20:09 +0530 Subject: [PATCH 15/20] Rename `X-topic` header to `X-channel` --- event-gateway/ARCHITECTURE.md | 10 ++--- event-gateway/WEBBROKERAPI.md | 40 +++++++++---------- .../websocket/broker_api_connector.go | 14 +++---- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/event-gateway/ARCHITECTURE.md b/event-gateway/ARCHITECTURE.md index 87acdbb6e..9a06a2059 100644 --- a/event-gateway/ARCHITECTURE.md +++ b/event-gateway/ARCHITECTURE.md @@ -261,7 +261,7 @@ WebBrokerApiBinding { │ 3. Create WebSocket Receiver │ │ - HTTP handler on context path │ │ - Metadata: channelNames, chainKeys │ -│ - Upgrade logic with X-topic validation │ +│ - Upgrade logic with X-channel validation │ └─────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ @@ -336,11 +336,11 @@ WebBrokerApiBinding { │ WebSocket │ │ Client │ └───────┬───────┘ - │ ws://gateway/api?X-topic=/issues + │ ws://gateway/api?X-channel=/issues ▼ ┌──────────────────────────────────────────┐ │ WebBrokerApiReceiver.handleUpgrade() │ -│ 1. Validate X-topic header │ +│ 1. Validate X-channel header │ │ 2. Apply onConnectionInit.request │ │ 3. Upgrade to WebSocket │ │ 4. Apply onConnectionInit.response │ @@ -659,7 +659,7 @@ spec: **WebSocket Connection:** ```bash -websocat --header "X-API-Key: secret" --header "X-topic: /issues" ws://gateway:8081/ws/events/v1 +websocat --header "X-API-Key: secret" --header "X-channel: /issues" ws://gateway:8081/ws/events/v1 ``` --- @@ -908,7 +908,7 @@ type Receiver interface { - Single topic per API - Uses `ProcessInbound()` policy enforcement only - `websocket-broker-api` - WebSocket receiver for multi-channel WebBrokerApi - - Supports multiple channels per API via `X-topic` header routing + - Supports multiple channels per API via `X-channel` header routing - Per-channel policy chains (onConnectionInit, onProduce, onConsume) - Separate produce/consume topics per channel - Designed for the WebBrokerApi specification diff --git a/event-gateway/WEBBROKERAPI.md b/event-gateway/WEBBROKERAPI.md index ba1216839..b113a310b 100644 --- a/event-gateway/WEBBROKERAPI.md +++ b/event-gateway/WEBBROKERAPI.md @@ -189,11 +189,11 @@ channels: **Client Usage:** ```javascript -// Connect with channel specified via X-topic header +// Connect with channel specified via X-channel header const ws = new WebSocket('ws://localhost:8080/ws-kafka', { headers: { 'X-API-Key': 'your-api-key', - 'X-topic': '/issues' // Select channel + 'X-channel': '/issues' // Select channel } }); @@ -298,7 +298,7 @@ Consumer group ID format: `{prefix}-ws-{uuid}` ## Channel Routing and Topic Subscription For WebBrokerApi: -- **Channel Selection**: Client specifies channel via `X-topic` header during WebSocket handshake (e.g., `X-topic: /issues`) +- **Channel Selection**: Client specifies channel via `X-channel` header during WebSocket handshake (e.g., `X-channel: /issues`) - **Per-Channel Topics**: Each channel defines its own topics via `map-topic` policies with `mode: produceTo` and `mode: consumeFrom` - **Consumer Subscription**: Each WebSocket connection subscribes only to topics configured for the selected channel - **Producer Publishing**: Messages are published to the topic specified in the channel's `on_produce` policies @@ -552,7 +552,7 @@ const WebSocket = require('ws'); const ws = new WebSocket('ws://localhost:8081/ws-kafka', { headers: { - 'X-topic': '/issues' + 'X-channel': '/issues' } }); @@ -747,7 +747,7 @@ INFO Registered WebBrokerApi binding name=websocket-kafka-api context=/ws-kafka Open another terminal and test with websocat: ```bash -websocat --header "X-topic: /issues" ws://localhost:8081/ws-kafka +websocat --header "X-channel: /issues" ws://localhost:8081/ws-kafka ``` Type messages and press Enter to send them to Kafka. @@ -851,11 +851,11 @@ Test with authentication: ```bash # Without API key (should fail) -websocat --header "X-topic: /secure-channel" ws://localhost:8081/secure-ws +websocat --header "X-channel: /secure-channel" ws://localhost:8081/secure-ws # → Connection rejected # With API key (should succeed) -websocat --header "X-API-Key: your-api-key" --header "X-topic: /secure-channel" ws://localhost:8081/secure-ws +websocat --header "X-API-Key: your-api-key" --header "X-channel: /secure-channel" ws://localhost:8081/secure-ws ``` ### Example: Multiple Channels with Different Topics @@ -903,12 +903,12 @@ Connect to different channels: ```javascript // Connect to /issues channel const ws1 = new WebSocket('ws://localhost:8081/ws-kafka', { - headers: { 'X-topic': '/issues' } + headers: { 'X-channel': '/issues' } }); // Connect to /commits channel const ws2 = new WebSocket('ws://localhost:8081/ws-kafka', { - headers: { 'X-topic': '/commits' } + headers: { 'X-channel': '/commits' } }); ``` @@ -1015,10 +1015,10 @@ docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-consumer.sh \ Connect to the `/issues` channel: ```bash -wscat -c ws://localhost:8081/ws-kafka --header "X-topic: /issues" +wscat -c ws://localhost:8081/ws-kafka --header "X-channel: /issues" ``` -**Expected Output:** +**Expected Output:**** ``` Connected (press CTRL+C to quit) > @@ -1106,7 +1106,7 @@ docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-producer.sh \ 1. Open **Terminal 4** with a second WebSocket client: ```bash - wscat -c ws://localhost:8081/ws-kafka --header "X-topic: /issues" + wscat -c ws://localhost:8081/ws-kafka --header "X-channel: /issues" ``` 2. In **Terminal 3** (Kafka producer), send a message: @@ -1219,10 +1219,10 @@ Note the `topics=[consume_issues]` - this confirms the consumer only subscribes - In static file mode, verify `channels.yaml` has the WebBrokerApi entry and `controlplane.enabled = false` **Connection rejected during handshake:** -- **Missing X-topic header**: Verify the `X-topic` header is included with a valid channel name (e.g., `X-topic: /issues`) +- **Missing X-channel header**: Verify the `X-channel` header is included with a valid channel name (e.g., `X-channel: /issues`) - Check `on_connection_init.request` policies (e.g., API key validation) - Verify headers are correctly set in upgrade request -- Check logs for: "Missing X-topic header" or "Unknown channel in X-topic header" +- Check logs for: "Missing X-channel header" or "Unknown channel in X-channel header" **Messages not reaching Kafka:** - Check `on_produce` policies for short-circuit conditions @@ -1498,13 +1498,13 @@ brew install websocat # macOS cargo install websocat # Linux/Rust # Basic usage -websocat --header "X-topic: /channel-name" ws://localhost:8081/ws-kafka +websocat --header "X-channel: /channel-name" ws://localhost:8081/ws-kafka # With headers -websocat --header "X-API-Key: test" --header "X-topic: /secure-channel" ws://localhost:8081/secure +websocat --header "X-API-Key: test" --header "X-channel: /secure-channel" ws://localhost:8081/secure # With text protocol -websocat -t --header "X-topic: /channel-name" ws://localhost:8081/ws-kafka +websocat -t --header "X-channel: /channel-name" ws://localhost:8081/ws-kafka ``` **wscat** - Alternative WebSocket CLI: @@ -1513,10 +1513,10 @@ websocat -t --header "X-topic: /channel-name" ws://localhost:8081/ws-kafka npm install -g wscat # Connect -wscat -c ws://localhost:8081/ws-kafka -H "X-topic: /channel-name" +wscat -c ws://localhost:8081/ws-kafka -H "X-channel: /channel-name" # With headers -wscat -c ws://localhost:8081/secure -H "X-API-Key: test" -H "X-topic: /secure-channel" +wscat -c ws://localhost:8081/secure -H "X-API-Key: test" -H "X-channel: /secure-channel" ``` **kcat (kafkacat)** - Kafka CLI tool: @@ -1642,7 +1642,7 @@ echo "test message" | kcat -b localhost:9092 -t repo-events -P 3. **Channel-Specific Configs**: New `channels` map where each key is a channel name (e.g., `/issues`, `/commits`) 4. **Per-Channel Policies**: Each channel has its own `on_connection_init`, `on_produce`, and `on_consume` policies 5. **Topic Extraction**: Topics no longer in `brokerDriver.properties.topic`; instead extracted from `map-topic` policies with `mode: produceTo` and `mode: consumeFrom` -6. **Channel Routing**: Client selects channel via `X-topic` header during WebSocket handshake +6. **Channel Routing**: Client selects channel via `X-channel` header during WebSocket handshake 7. **Policy Cascade**: API-level policies execute first, then channel-specific policies ## Next Steps diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go index df50b360e..5986d18d2 100644 --- a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -64,7 +64,7 @@ type brokerApiConnection struct { cancel context.CancelFunc closed bool mu sync.Mutex - channelName string // Selected channel from X-topic header + channelName string // Selected channel from X-channel header produceChainKey string // Policy chain key for on_produce consumeChainKey string // Policy chain key for on_consume produceTopic string // Target Kafka topic for producing messages @@ -139,12 +139,12 @@ func (e *WebBrokerApiReceiver) Stop(ctx context.Context) error { // handleUpgrade handles WebSocket upgrade requests. func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Request) { - // Extract channel name from X-topic header. - xTopicHeader := r.Header.Get("X-topic") - channelName := xTopicHeader + // Extract channel name from X-channel header. + xChannelHeader := r.Header.Get("X-channel") + channelName := xChannelHeader if channelName == "" { - slog.Error("Missing X-topic header in WebSocket connection", "api", e.channel.Name, "remote", r.RemoteAddr) - http.Error(w, "Missing X-topic header", http.StatusBadRequest) + slog.Error("Missing X-channel header in WebSocket connection", "api", e.channel.Name, "remote", r.RemoteAddr) + http.Error(w, "Missing X-channel header", http.StatusBadRequest) return } @@ -172,7 +172,7 @@ func (e *WebBrokerApiReceiver) handleUpgrade(w http.ResponseWriter, r *http.Requ } if !channelExists { - slog.Error("Unknown channel in X-topic header", "api", e.channel.Name, "channel", channelName, "remote", r.RemoteAddr) + slog.Error("Unknown channel in X-channel header", "api", e.channel.Name, "channel", channelName, "remote", r.RemoteAddr) http.Error(w, fmt.Sprintf("Unknown channel: %s", channelName), http.StatusNotFound) return } From b4f4828fe904780c7c97b9bbcf8c63ff2404a3e7 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 19:45:58 +0530 Subject: [PATCH 16/20] Update readme --- event-gateway/README.md | 141 +++ event-gateway/WEBBROKERAPI.md | 1653 --------------------------------- 2 files changed, 141 insertions(+), 1653 deletions(-) delete mode 100644 event-gateway/WEBBROKERAPI.md diff --git a/event-gateway/README.md b/event-gateway/README.md index 1d394f4cf..b7b5b9d62 100644 --- a/event-gateway/README.md +++ b/event-gateway/README.md @@ -218,6 +218,147 @@ docker compose logs wh-listener You should see the event body and headers printed by the listener. +### WebBrokerApi Walkthrough + +The WebBrokerApi enables bidirectional WebSocket ↔ Kafka protocol mediation. This walkthrough demonstrates creating a stock trading API where clients can produce messages to Kafka and consume messages in real-time over WebSocket. + +#### Step 1: Create a WebBroker API + +Use the following curl command to create a WebBrokerApi with a `prices` channel that maps to Kafka topics: + +```bash +curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--header 'Authorization: Basic YWRtaW46YWRtaW4=' \ +--data '{ + "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", + "kind": "WebBrokerApi", + "metadata": { + "name": "stock-trading-v1.0" + }, + "spec": { + "displayName": "Stock Trading WebBroker API", + "version": "v1.0", + "context": "/stock-trading/v1.0", + "receiver": { + "name": "websocket-receiver", + "type": "websocket" + }, + "broker": { + "name": "kafka-driver", + "type": "kafka", + "properties": { + "brokers": [ + "kafka:29092" + ] + } + }, + "allChannels": { + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + }, + "channels": { + "prices": { + "produceTo": { + "topic": "stock.prices" + }, + "consumeFrom": { + "topic": "dummy.prices" + }, + "on_connection_init": { + "policies": [] + }, + "on_produce": { + "policies": [] + }, + "on_consume": { + "policies": [] + } + } + } + } +}' +``` + +This creates a WebBrokerApi where: +- Client messages are published to the `stock.prices` Kafka topic +- Messages from the `dummy.prices` Kafka topic are delivered to the WebSocket client + +#### Step 2: Connect via WebSocket + +Install `wscat` if you haven't already: + +```bash +npm install -g wscat +``` + +Connect to the WebBroker API and select the `prices` channel using the `X-channel` header: + +```bash +wscat -c ws://localhost:8081/stock-trading/v1.0 -H "X-channel: prices" +``` + +Once connected, you'll see: +``` +Connected (press CTRL+C to quit) +> +``` + +#### Step 3: Monitor Messages Published to Kafka + +In a new terminal, start a Kafka consumer to monitor messages that clients send via WebSocket: + +```bash +docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-consumer.sh \ + --bootstrap-server localhost:9092 \ + --topic stock.prices \ + --from-beginning +``` + +Now, type a message in your WebSocket terminal (Step 2) and press Enter: + +``` +> {"symbol": "AAPL", "price": 150.25, "timestamp": "2026-05-13T10:30:00Z"} +``` + +The message should appear in the Kafka consumer terminal immediately. + +#### Step 4: Publish Messages from Kafka to WebSocket + +In another terminal, start a Kafka producer to send messages that will be delivered to WebSocket clients: + +```bash +docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-producer.sh \ + --bootstrap-server localhost:9092 \ + --topic dummy.prices +``` + +Type a message in the Kafka producer terminal and press Enter: + +``` +> {"symbol": "GOOGL", "price": 2750.50, "timestamp": "2026-05-13T10:31:00Z"} +``` + +The message should appear in your WebSocket terminal (Step 2): + +``` +< {"symbol": "GOOGL", "price": 2750.50, "timestamp": "2026-05-13T10:31:00Z"} +``` + +**Key Points:** +- WebSocket → Kafka: Messages typed in wscat are published to `stock.prices` +- Kafka → WebSocket: Messages published to `dummy.prices` are delivered to the WebSocket client +- Bidirectional: Both directions work simultaneously over the same WebSocket connection +- Per-Connection Isolation: Each WebSocket connection gets its own Kafka consumer group + ### Other Control Plane Operations | Request | Method | URL | diff --git a/event-gateway/WEBBROKERAPI.md b/event-gateway/WEBBROKERAPI.md deleted file mode 100644 index b113a310b..000000000 --- a/event-gateway/WEBBROKERAPI.md +++ /dev/null @@ -1,1653 +0,0 @@ -# WebBrokerApi: Protocol Mediation - -## Table of Contents - -- [Overview](#overview) -- [Architecture](#architecture) - - [Key Components](#key-components) - - [Per-Connection Model](#per-connection-model) - - [Message Flows](#message-flows) -- [Policy Enforcement Points](#policy-enforcement-points) -- [Specification Format](#specification-format) -- [Example Use Cases](#example-use-cases) -- [Building and Running](#building-and-running) - - [Option 1: Using Docker Compose (Recommended)](#option-1-using-docker-compose-recommended) - - [Option 2: Building from Source](#option-2-building-from-source) - - [Option 3: Development Mode with Live Reload](#option-3-development-mode-with-live-reload) -- [Testing with Policies](#testing-with-policies) -- [End-to-End Testing with Kafka](#end-to-end-testing-with-kafka) -- [Implementation Details](#implementation-details) -- [Comparison: WebSubApi vs WebBrokerApi](#comparison-websubapi-vs-webbrokerapi) -- [Consumer Group Strategy](#consumer-group-strategy) -- [Topic Subscription](#topic-subscription) -- [Troubleshooting](#troubleshooting) -- [Quick Reference](#quick-reference) -- [Future Enhancements](#future-enhancements) -- [Next Steps](#next-steps) - -## Overview - -**WebBrokerApi** is a new binding type in the Event Gateway that enables **protocol mediation** between web-friendly protocols (WebSocket, SSE) and message brokers (Kafka, MQTT, AMQP). It provides bidirectional streaming with per-connection isolation. - -## Architecture - -### Key Components - -1. **Receiver**: Protocol adapter for web-friendly clients (WebSocket, SSE) -2. **Broker Driver**: Message broker adapter (Kafka, MQTT, AMQP) -3. **Policy Engine**: Message processing with three enforcement points - -### Per-Connection Model - -Each WebSocket connection gets: -- **Inbound Go Channel**: Handles messages from client → broker (produce path) -- **Outbound Go Channel**: Handles messages from broker → client (consume path) -- **Dedicated Kafka Consumer**: Unique consumer group per connection -- **Shared Kafka Producer**: Can publish to any topic dynamically - -### Message Flows - -**Produce Path** (Client → Broker): -``` -WebSocket Client → Receiver → Inbound Channel → on_produce policies → Broker Driver → Kafka -``` - -**Consume Path** (Broker → Client): -``` -Kafka → Broker Driver → Outbound Channel → on_consume policies → Receiver → WebSocket Client -``` - -## Policy Enforcement Points - -Unlike WebSub's `subscribe/inbound/outbound`, WebBrokerApi has: - -| Policy Point | When Applied | Purpose | -|--------------|-------------|---------| -| `on_connection_init.request` | WebSocket handshake (before upgrade) | Authentication, authorization | -| `on_connection_init.response` | WebSocket handshake (after upgrade) | Response customization | -| `on_produce` | Client sends message to broker | Topic mapping, validation, transformation | -| `on_consume` | Broker message delivered to client | Filtering, transformation | - -## Specification Format - -```yaml -kind: WebBrokerApi -apiId: unique-api-identifier -name: api-name -version: v1.0 -context: /base-path - -receiver: - name: receiver-instance-name # Instance identifier - type: websocket # or "sse" in the future - properties: {} - -brokerDriver: - name: broker-instance-name # Instance identifier - type: kafka # or "mqtt", "amqp" in the future - properties: - bootstrap.servers: localhost:9092 - security.protocol: PLAINTEXT - -policies: # API-level policies applied to all channels - on_connection_init: - request: - - name: policy-name - version: v1 - params: {} - response: [] - on_produce: - - name: policy-name - version: v1 - params: {} - on_consume: [] - -channels: # Channel-specific configurations with per-channel policies - "/channel-name": - policies: - onConnectionInit: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-topic-name - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-topic-name -``` - -## Example Use Cases - -### 1. WebSocket to Kafka with Channel Routing - -```yaml -channels: - - kind: WebBrokerApi - name: websocket-kafka-api - version: v1.0 - context: /ws-kafka - receiver: - name: ws-receiver - type: websocket - brokerDriver: - name: kafka-driver - type: kafka - properties: - bootstrap.servers: localhost:9092 - policies: # API-level policies - on_connection_init: - request: - - name: api-key-auth - version: v1 - params: - in: header - name: X-API-Key - on_produce: [] - on_consume: [] - channels: # Per-channel configurations - "/issues": - policies: - onConnectionInit: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-repo-issues - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-repo-issues - "/commits": - policies: - onConnectionInit: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-repo-commits - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-repo-commits -``` - -**Client Usage:** -```javascript -// Connect with channel specified via X-channel header -const ws = new WebSocket('ws://localhost:8080/ws-kafka', { - headers: { - 'X-API-Key': 'your-api-key', - 'X-channel': '/issues' // Select channel - } -}); - -// Send message (will produce to kafka-repo-issues) -ws.send(JSON.stringify({ - headers: { - 'X-Client-Topic': 'client-issues' - }, - body: { issue: 'Bug report' } -})); - -// Consume from all subscribed topics -ws.onmessage = (event) => { - console.log('Received:', event.data); -}; -``` - -## Implementation Details - -### Files Modified/Created - -1. **`internal/binding/types.go`** - - Added `WebBrokerApiBinding` struct - - Added `ProtocolMediationPolicies` struct - - Added `ConnectionInitPolicies` struct - -2. **`internal/binding/loader.go`** - - Updated `ParseResult` to include `WebBrokerApiBindings` - - Added `WebBrokerApi` case in parser - -3. **`internal/hub/hub.go`** - - Added `ProcessConnectionInitRequest()` - - Added `ProcessConnectionInitResponse()` - - Added `ProcessProduce()` - - Added `ProcessConsume()` - -4. **`internal/connectors/types.go`** - - Updated `MessageProcessor` interface with new methods - - Added `Topics` field to `ChannelInfo` - -5. **`internal/connectors/receiver/websocket/broker_api_connector.go`** (NEW) - - Implemented `WebBrokerApiReceiver` - - Per-connection bidirectional streaming - - Dedicated Kafka consumer/producer per connection - -6. **`internal/runtime/runtime.go`** - - Added WebBrokerApi processing in `LoadChannels()` - - Added `buildWebBrokerApiPolicyChains()` - -7. **`cmd/event-gateway/plugins.go`** - - Registered `websocket-broker-api` receiver factory - -### Connection Lifecycle - -1. **WebSocket Upgrade**: - - Client sends upgrade request - - `on_connection_init.request` policies applied - - If short-circuited, reject with policy-defined response - - Upgrade to WebSocket - - `on_connection_init.response` policies applied - -2. **Resource Creation**: - - Unique connection ID generated - - Inbound/outbound Go channels created - - Unique Kafka consumer group: `{prefix}-ws-{connID}` - - Kafka consumer subscribes to all relevant topics - - Kafka producer created for publishing - -3. **Message Processing**: - - **Read loop**: WebSocket → Inbound channel - - **Inbound loop**: Inbound channel → on_produce policies → Kafka - - **Outbound loop**: Outbound channel → on_consume policies → WebSocket - - **Consumer callback**: Kafka → Outbound channel - -4. **Connection Close**: - - Stop Kafka consumer - - Close inbound/outbound channels - - Close WebSocket connection - - Clean up connection from registry - -## Comparison: WebSubApi vs WebBrokerApi - -| Aspect | WebSubApi | WebBrokerApi | -|--------|-----------|--------------| -| **Use Case** | Async pub/sub with HTTP callbacks | Bidirectional streaming | -| **Protocol** | HTTP (POST to callbacks) | WebSocket, SSE | -| **Connection** | Stateless HTTP | Persistent streaming | -| **Isolation** | Per-callback consumer group | Per-connection consumer group | -| **Topics** | Multiple channels per API | Dynamic via policies | -| **Policy Points** | subscribe/inbound/outbound | connection_init/produce/consume | -| **Direction** | Unidirectional (gateway → callback) | Bidirectional (client ↔ broker) | - -## Consumer Group Strategy - -Each WebSocket connection gets a **unique consumer group** to ensure: -- Independent consumption (not load-balanced) -- Each client receives all messages -- No message loss on connection drop (offset tracked per connection) - -Consumer group ID format: `{prefix}-ws-{uuid}` - -## Channel Routing and Topic Subscription - -For WebBrokerApi: -- **Channel Selection**: Client specifies channel via `X-channel` header during WebSocket handshake (e.g., `X-channel: /issues`) -- **Per-Channel Topics**: Each channel defines its own topics via `map-topic` policies with `mode: produceTo` and `mode: consumeFrom` -- **Consumer Subscription**: Each WebSocket connection subscribes only to topics configured for the selected channel -- **Producer Publishing**: Messages are published to the topic specified in the channel's `on_produce` policies -- **Policy Cascade**: API-level policies execute first, followed by channel-specific policies - -## Future Enhancements - -1. **SSE Support**: Add Server-Sent Events receiver -2. **More Brokers**: Add MQTT, AMQP, NATS broker drivers -3. **Topic Discovery**: Dynamic topic subscription based on client requests -4. **Connection Pooling**: Shared consumer groups for load balancing (optional mode) -5. **Backpressure Control**: Configurable buffer sizes and overflow strategies -6. **Metrics**: Per-connection throughput, latency, error rates - -## Building and Running - -The event gateway supports two configuration modes: - -1. **Control Plane Mode (Recommended)**: Configure APIs through the gateway-controller REST API. The controller distributes configurations to the event-gateway via xDS protocol. This is the default mode in Docker Compose. - -2. **Static File Mode**: Configure APIs by editing the `channels.yaml` file directly. Useful for development and testing without the controller. - -### Prerequisites - -- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) -- [Go 1.24+](https://go.dev/dl/) (for building from source) -- [Kafka](https://kafka.apache.org/) (provided via Docker Compose) - -### Setup: Copy Shared Configuration - -The event-gateway reuses the gateway-controller configuration from the main gateway. Before starting, copy the shared config: - -```bash -# From the api-platform root directory -cp gateway/configs/config.toml event-gateway/configs/gateway-controller/config.toml -``` - -This config contains: -- REST API authentication credentials (default: `admin:admin`) -- Gateway identification and control plane settings -- Logging configuration - -**Note:** Whenever you update credentials or settings in `gateway/configs/config.toml`, remember to sync it to the event-gateway: -```bash -cp gateway/configs/config.toml event-gateway/configs/gateway-controller/config.toml -``` - -### Option 1: Using Docker Compose (Recommended) - -This is the easiest way to test protocol mediation with all dependencies. - -#### 1. Start All Services - -From the `event-gateway/` directory: - -```bash -# Copy environment template -cp .env.example .env - -# Start all services (Kafka, Event Gateway, Controller) -docker compose up -d - -# Check status -docker compose ps - -# View logs -docker compose logs -f event-gateway -``` - -This starts: -- **Kafka** on `localhost:9092` (external) and `kafka:29092` (internal) -- **Event Gateway** on `localhost:8081` (WebSocket), `localhost:8080` (HTTP), `localhost:9002` (Admin API) -- **Gateway Controller** on `localhost:9090` (Management API), `localhost:18001` (xDS) - -**Note:** TLS is currently disabled for local development. To enable HTTPS: -1. Generate certificates in `listener-certs/` directory -2. Set `websub_tls_enabled = false` in `gateway-runtime/configs/config.toml` -3. Restart services with `docker compose restart event-gateway` - -#### 2. Create WebBrokerApi via Control Plane (Recommended) - -The gateway runs in control plane mode by default, which means you configure APIs through the gateway-controller REST API: - -```bash -# Create a WebBrokerApi via the gateway-controller -curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ ---header 'Content-Type: application/json' \ ---header 'Authorization: Basic YWRtaW46YWRtaW4=' \ ---data '{ - "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", - "kind": "WebBrokerApi", - "metadata": { "name": "websocket-kafka-api-v1-0" }, - "spec": { - "displayName": "websocket-kafka-api", - "version": "v1.0", - "context": "/ws-kafka", - "receiver": { - "name": "ws-receiver", - "type": "websocket", - "properties": {} - }, - "brokerDriver": { - "name": "kafka-driver", - "type": "kafka", - "properties": { - "bootstrap.servers": "kafka:29092" - } - }, - "policies": { - "onConnectionInit": { - "request": [], - "response": [] - }, - "onProduce": [], - "onConsume": [] - }, - "channels": { - "/issues": { - "policies": { - "onConnectionInit": { - "request": [], - "response": [] - }, - "on_produce": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "produceTo", - "topic": "repo-events" - } - } - ], - "on_consume": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "consumeFrom", - "topic": "repo-events" - } - } - ] - } - } - }, - "deploymentState": "deployed" - } - }' -``` - -Verify it was created: - -```bash -# List all WebBrokerApis -curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis \ - -u admin:admin - -# Get specific WebBrokerApi -curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis/websocket-kafka-api-v1-0 \ - -u admin:admin -``` - -The controller automatically distributes the configuration to the event-gateway via xDS. - -**Alternative: Static File Mode** - -If you prefer to use static configuration files instead of the control plane: - -1. Disable control plane in `docker-compose.yaml`: - ```yaml - environment: - - APIP_EGW_CONTROLPLANE_ENABLED=false - ``` - -2. Edit `gateway-runtime/configs/channels.yaml`: - ```yaml - channels: - - kind: WebBrokerApi - apiId: websocket-kafka-api-v1-0 - name: websocket-kafka-api - version: v1.0 - context: /ws-kafka - receiver: - type: websocket - properties: {} - broker-driver: - type: kafka - properties: - topic: repo-events - bootstrap.servers: kafka:29092 - allChannelPolicies: - on_connection_init: - request: [] - response: [] - on_produce: [] - on_consume: [] - ``` - -3. Restart services: - ```bash - docker compose restart event-gateway - ``` - -#### 3. Verify Event Gateway is Running - -```bash -# Health check -curl http://localhost:9002/health -# → {"status":"UP"} - -# Check that xDS distributed the config (should show the WebBrokerApi) -docker compose logs event-gateway | grep "WebBrokerApi" - -# Check WebSocket endpoint -curl -I http://localhost:8081/ws-kafka -# → HTTP/1.1 426 Upgrade Required (means WebSocket endpoint is ready) -``` - -#### 4. Test with WebSocket Client - -**Using wscat (CLI tool):** - -```bash -# Install wscat -npm install -g wscat - -# Connect to the WebSocket endpoint -wscat -c ws://localhost:8081/ws-kafka - -# Once connected, type messages and press Enter to send to Kafka -# You should see messages echoed back as they're consumed from Kafka -``` - -**Using websocat (alternative CLI tool):** - -```bash -# Install websocat -brew install websocat # macOS -# or download from https://github.com/vi/websocat - -# Connect and send/receive messages -websocat ws://localhost:8081/ws-kafka -``` - -**Using Node.js:** - -```javascript -// test-websocket.js -const WebSocket = require('ws'); - -const ws = new WebSocket('ws://localhost:8081/ws-kafka', { - headers: { - 'X-channel': '/issues' - } -}); - -ws.on('open', () => { - console.log('Connected to Event Gateway'); - - // Send a message to Kafka - ws.send(JSON.stringify({ - message: 'Hello Kafka from WebSocket!', - timestamp: new Date().toISOString() - })); -}); - -ws.on('message', (data) => { - console.log('Received from Kafka:', data.toString()); -}); - -ws.on('close', () => { - console.log('Disconnected'); -}); - -ws.on('error', (error) => { - console.error('Error:', error); -}); -``` - -Run it: -```bash -npm install ws -node test-websocket.js -``` - -**Using Browser Console:** - -```javascript -// Note: Browser WebSocket API doesn't support custom headers in constructor -// Use Sec-WebSocket-Protocol or URL query parameters as alternatives -const ws = new WebSocket('ws://localhost:8081/ws-kafka?channel=/issues'); - -ws.onopen = () => { - console.log('Connected!'); - ws.send('Hello from browser!'); -}; - -ws.onmessage = (event) => { - console.log('Received:', event.data); -}; -``` - -#### 5. Monitor Kafka Topics - -```bash -# View messages in Kafka UI -open http://localhost:7080 - -# Or use Kafka CLI -docker exec -it event-gateway-kafka-1 kafka-console-consumer \ - --bootstrap-server localhost:29092 \ - --topic repo-events \ - --from-beginning -``` - -#### 6. Stop Services - -```bash -# Stop all services -docker compose down - -# Stop and remove volumes (clean slate) -docker compose down -v -``` - -### Option 2: Building from Source - -For development or when you need to modify the code. - -#### 1. Build the Event Gateway Runtime - -```bash -cd event-gateway/gateway-runtime - -# Build the binary -go build -o event-gateway ./cmd/event-gateway - -# Or use the Makefile (builds Docker image) -cd .. -make build-gateway-runtime -``` - -#### 2. Start Dependencies (Kafka only) - -```bash -# Start just Kafka from docker-compose -docker compose up kafka -d - -# Wait for Kafka to be ready -docker compose logs -f kafka -``` - -#### 3. Configure Channels - -Create or edit `gateway-runtime/configs/channels.yaml`: - -```yaml -channels: - - kind: WebBrokerApi - name: websocket-kafka-api - version: v1.0 - context: /ws-kafka - receiver: - name: ws-receiver - type: websocket - broker-driver: - name: kafka-driver - type: kafka - properties: - bootstrap.servers: localhost:9092 - policies: - on_connection_init: - request: [] - response: [] - on_produce: [] - on_consume: [] - channels: - "/issues": - policies: - onConnectionInit: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: repo-events - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: repo-events -``` - -#### 4. Configure Gateway - -Edit `gateway-runtime/configs/config.toml`: - -```toml -[kafka] -brokers = ["localhost:9092"] -consumer_group_prefix = "egw" - -[server] -websocket_port = 8081 -websub_enabled = false -websub_tls_enabled = false # TLS disabled for local dev -admin_port = 9002 - -[controlplane] -enabled = false # Run in static mode (set to true to use gateway-controller) - -[logging] -level = "info" -``` - -**Note:** When `controlplane.enabled = false`, the gateway reads configuration from the local `channels.yaml` file. When `enabled = true`, it connects to the gateway-controller at `xds_address` and receives configuration via xDS. - -#### 5. Run the Event Gateway - -```bash -cd gateway-runtime - -# Run with config and channels files -./event-gateway \ - -config configs/config.toml \ - -channels configs/channels.yaml - -# Or if you didn't build, run directly with Go -go run ./cmd/event-gateway \ - -config configs/config.toml \ - -channels configs/channels.yaml -``` - -You should see: -``` -INFO Event gateway is ready runtime_id=... -INFO Registered WebBrokerApi binding name=websocket-kafka-api context=/ws-kafka channels=1 topics=[repo-events] -``` - -#### 6. Test the Connection - -Open another terminal and test with websocat: - -```bash -websocat --header "X-channel: /issues" ws://localhost:8081/ws-kafka -``` - -Type messages and press Enter to send them to Kafka. - -#### 7. Verify Messages in Kafka - -```bash -# View messages being published -docker exec -it event-gateway-kafka-1 kafka-console-consumer \ - --bootstrap-server localhost:29092 \ - --topic repo-events \ - --from-beginning -``` - -### Option 3: Development Mode with Live Reload - -For rapid iteration during development. - -#### 1. Install Air (Live Reload Tool) - -```bash -go install github.com/cosmtrek/air@latest -``` - -#### 2. Configure Air - -Create `.air.toml` in `gateway-runtime/`: - -```toml -root = "." -tmp_dir = "tmp" - -[build] -cmd = "go build -o ./tmp/event-gateway ./cmd/event-gateway" -bin = "./tmp/event-gateway -config configs/config.toml -channels configs/channels.yaml" -include_ext = ["go", "toml", "yaml"] -exclude_dir = ["tmp", "vendor"] -delay = 1000 -``` - -#### 3. Run with Live Reload - -```bash -cd gateway-runtime -air -``` - -Now any changes to Go files will automatically rebuild and restart the gateway. - -## Testing with Policies - -### Example: API Key Authentication - -Update your `channels.yaml`: - -```yaml -channels: - - kind: WebBrokerApi - name: secure-websocket-api - version: v1.0 - context: /secure-ws - receiver: - name: ws-receiver - type: websocket - broker-driver: - name: kafka-driver - type: kafka - properties: - bootstrap.servers: kafka:29092 - policies: - on_connection_init: - request: - - name: api-key-auth - version: v1 - params: - in: header - name: X-API-Key - on_produce: [] - on_consume: [] - channels: - "/secure-channel": - policies: - onConnectionInit: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: secure-events - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: secure-events -``` - -Test with authentication: - -```bash -# Without API key (should fail) -websocat --header "X-channel: /secure-channel" ws://localhost:8081/secure-ws -# → Connection rejected - -# With API key (should succeed) -websocat --header "X-API-Key: your-api-key" --header "X-channel: /secure-channel" ws://localhost:8081/secure-ws -``` - -### Example: Multiple Channels with Different Topics - -```yaml -channels: - "/issues": - policies: - onConnectionInit: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-repo-issues - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-repo-issues - "/commits": - policies: - onConnectionInit: - request: [] - response: [] - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: kafka-repo-commits - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: kafka-repo-commits -``` - -Connect to different channels: - -```javascript -// Connect to /issues channel -const ws1 = new WebSocket('ws://localhost:8081/ws-kafka', { - headers: { 'X-channel': '/issues' } -}); - -// Connect to /commits channel -const ws2 = new WebSocket('ws://localhost:8081/ws-kafka', { - headers: { 'X-channel': '/commits' } -}); -``` - -## End-to-End Testing with Kafka - -This section demonstrates how to verify the complete WebSocket ↔ Kafka flow in both directions. - -### Prerequisites - -- Docker Compose services running (`docker compose up -d`) -- WebBrokerApi created with separate produce and consume topics (recommended for testing) -- `wscat` installed (`npm install -g wscat`) - -### Example API with Topic Separation - -For clear testing, configure a WebBrokerApi with **separate topics** for produce and consume: - -```bash -curl --location 'http://localhost:9090/api/management/v0.9/webbroker-apis' \ ---header 'Content-Type: application/json' \ ---header 'Authorization: Basic YWRtaW46YWRtaW4=' \ ---data '{ - "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", - "kind": "WebBrokerApi", - "metadata": { "name": "websocket-kafka-api-v1-0" }, - "spec": { - "displayName": "websocket-kafka-api", - "version": "v1.0", - "context": "/ws-kafka", - "receiver": { - "name": "ws-receiver", - "type": "websocket", - "properties": {} - }, - "brokerDriver": { - "name": "kafka-driver", - "type": "kafka", - "properties": { - "bootstrap.servers": "kafka:29092" - } - }, - "policies": { - "onConnectionInit": { "request": [], "response": [] }, - "onProduce": [], - "onConsume": [] - }, - "channels": { - "/issues": { - "policies": { - "onConnectionInit": { - "request": [], - "response": [] - }, - "on_produce": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "produceTo", - "topic": "produce_issues" - } - } - ], - "on_consume": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "consumeFrom", - "topic": "consume_issues" - } - } - ] - } - } - }, - "deploymentState": "deployed" - } - }' -``` - -**Key Points:** -- `/issues` channel publishes to `produce_issues` topic (one-way) -- `/issues` channel consumes from `consume_issues` topic (one-way) -- **No echo/loopback**: Messages sent via WebSocket don't come back to the sender - -### Test Setup: 3 Terminals - -#### Terminal 1: Kafka Consumer (Monitor WebSocket → Kafka) - -This terminal monitors the `produce_issues` topic to verify messages from WebSocket clients arrive in Kafka: - -```bash -docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-consumer.sh \ - --bootstrap-server localhost:9092 \ - --topic produce_issues \ - --from-beginning -``` - -**Expected Output:** All messages sent via WebSocket will appear here. - -#### Terminal 2: WebSocket Client - -Connect to the `/issues` channel: - -```bash -wscat -c ws://localhost:8081/ws-kafka --header "X-channel: /issues" -``` - -**Expected Output:**** -``` -Connected (press CTRL+C to quit) -> -``` - -#### Terminal 3: Kafka Producer (Send to WebSocket Clients) - -This terminal publishes to the `consume_issues` topic, which will be delivered to WebSocket clients: - -```bash -docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-console-producer.sh \ - --bootstrap-server localhost:9092 \ - --topic consume_issues -``` - -**Expected Output:** -``` -> -``` - -### Test 1: WebSocket → Kafka (Produce Path) - -**Objective:** Verify messages sent from WebSocket client arrive in Kafka. - -**Steps:** - -1. In **Terminal 2** (WebSocket client), type a message and press Enter: - ``` - > {"message": "Hello from WebSocket", "timestamp": "2026-05-11T12:00:00Z"} - ``` - -2. In **Terminal 1** (Kafka consumer), verify the message appears: - ``` - {"message": "Hello from WebSocket", "timestamp": "2026-05-11T12:00:00Z"} - ``` - -3. Send multiple messages to test throughput: - ``` - > {"event": "issue_created", "id": 1} - > {"event": "issue_updated", "id": 1, "status": "in-progress"} - > {"event": "issue_closed", "id": 1} - ``` - -4. Verify all messages appear in **Terminal 1** in order. - -**✅ Success Criteria:** -- All messages from WebSocket appear in the `produce_issues` Kafka topic -- Messages maintain order -- No messages echo back to WebSocket client (Terminal 2 shows only `>` prompt) - -### Test 2: Kafka → WebSocket (Consume Path) - -**Objective:** Verify messages published to Kafka are delivered to WebSocket clients. - -**Steps:** - -1. In **Terminal 3** (Kafka producer), type a message and press Enter: - ``` - > {"message": "Hello from Kafka", "source": "external-service"} - ``` - -2. In **Terminal 2** (WebSocket client), verify the message is received: - ``` - < {"message": "Hello from Kafka", "source": "external-service"} - ``` - -3. Send multiple messages from Kafka: - ``` - > {"event": "notification", "type": "email", "status": "sent"} - > {"event": "notification", "type": "sms", "status": "delivered"} - ``` - -4. Verify all messages appear in **Terminal 2** immediately. - -**✅ Success Criteria:** -- All messages from `consume_issues` Kafka topic appear in WebSocket client -- Messages are delivered in real-time (< 1 second delay) -- Messages maintain order - -### Test 3: Multiple WebSocket Connections - -**Objective:** Verify each connection receives messages independently. - -**Steps:** - -1. Open **Terminal 4** with a second WebSocket client: - ```bash - wscat -c ws://localhost:8081/ws-kafka --header "X-channel: /issues" - ``` - -2. In **Terminal 3** (Kafka producer), send a message: - ``` - > {"broadcast": "message to all clients"} - ``` - -3. Verify the message appears in **both Terminal 2 and Terminal 4**. - -4. In **Terminal 2**, send a message: - ``` - > {"from": "client1"} - ``` - -5. Verify in **Terminal 1** (Kafka consumer) that the message appears. - -6. Verify in **Terminal 2 and Terminal 4** that the message does NOT echo back. - -**✅ Success Criteria:** -- Each WebSocket connection has its own unique consumer group -- All connections receive broadcast messages from Kafka -- Messages sent by one client don't echo to any WebSocket client -- Each connection can produce independently - -### Test 4: Verify Topic Isolation - -**Objective:** Confirm produce and consume topics are completely isolated. - -**Steps:** - -1. List Kafka topics to verify both exist: - ```bash - docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-topics.sh \ - --bootstrap-server localhost:9092 \ - --list | grep issues - ``` - - **Expected Output:** - ``` - consume_issues - produce_issues - ``` - -2. Check message count in `produce_issues`: - ```bash - docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-run-class.sh \ - kafka.tools.GetOffsetShell \ - --broker-list localhost:9092 \ - --topic produce_issues - ``` - -3. Check message count in `consume_issues`: - ```bash - docker exec -it event-gateway-kafka-1 /opt/kafka/bin/kafka-run-class.sh \ - kafka.tools.GetOffsetShell \ - --broker-list localhost:9092 \ - --topic consume_issues - ``` - -4. Verify counts match your test messages (produce_issues has WebSocket messages, consume_issues has Kafka messages). - -**✅ Success Criteria:** -- Topics are independent with different message counts -- No cross-contamination between produce and consume paths - -### Debugging Tips - -**Check Event Gateway Logs:** -```bash -docker compose logs -f event-gateway | grep -E "Map Topic|Publishing|received from WebSocket" -``` - -**Verify Channel Configuration:** -```bash -docker compose logs event-gateway | grep "Built policy chains for WebBrokerApi channel" -``` - -**Expected Log Output:** -``` -level=INFO msg="Built policy chains for WebBrokerApi channel" api=websocket-kafka-api-v1-0 channel=/issues topics="[produce_issues consume_issues]" -``` - -**Check WebSocket Connection:** -```bash -docker compose logs event-gateway | grep "WebSocket handshake completed" -``` - -**Expected Log Output:** -``` -level=INFO msg="[4] WebSocket handshake completed" connID=xxx api=websocket-kafka-api-v1-0 channel=/issues topics=[consume_issues] -``` - -Note the `topics=[consume_issues]` - this confirms the consumer only subscribes to the consume topic, not the produce topic. - -## Troubleshooting - -**WebSocket endpoint not available (Empty reply from server):** -- In control plane mode, verify the WebBrokerApi was created via the controller: - ```bash - curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis -u admin:admin - ``` -- Check event-gateway logs for xDS configuration: - ```bash - docker compose logs event-gateway | grep -E "EventChannelConfig|WebBrokerApi" - ``` -- Verify control plane is connected: - ```bash - docker compose logs event-gateway | grep "Connected to xDS" - ``` -- In static file mode, verify `channels.yaml` has the WebBrokerApi entry and `controlplane.enabled = false` - -**Connection rejected during handshake:** -- **Missing X-channel header**: Verify the `X-channel` header is included with a valid channel name (e.g., `X-channel: /issues`) -- Check `on_connection_init.request` policies (e.g., API key validation) -- Verify headers are correctly set in upgrade request -- Check logs for: "Missing X-channel header" or "Unknown channel in X-channel header" - -**Messages not reaching Kafka:** -- Check `on_produce` policies for short-circuit conditions -- Verify topic mapping in policies -- Check Kafka broker connectivity - -**Messages not reaching client:** -- Check `on_consume` policies for short-circuit conditions -- Verify WebSocket connection is still open -- Check outbound channel buffer (may be full) - -**High memory usage:** -- Too many concurrent connections -- Adjust buffer sizes in receiver config -- Consider implementing connection limits - -**Connection timeout during Kafka operations:** -- Verify Kafka is running: `docker compose ps kafka` -- Check Kafka logs: `docker compose logs kafka` -- Verify bootstrap servers address (use `kafka:29092` in Docker, `localhost:9092` on host) - -**WebSocket connection closes immediately:** -- Check event gateway logs: `docker compose logs event-gateway` -- Verify the context path matches your channels.yaml config -- Check for policy errors during connection init - -**Gateway container exits with "config.toml is a directory" error:** -- The event-gateway reuses the main gateway config from `../gateway/configs/config.toml` -- Ensure that file exists and is not a directory -- Restart services: `docker compose up -d` - -**Event gateway exits with "TLS certificate file does not exist":** -- TLS is enabled but certificates are missing in `listener-certs/` directory -- **Solution 1 (Recommended for local dev):** Disable TLS by setting `websub_tls_enabled = false` in `gateway-runtime/configs/config.toml` -- **Solution 2:** Generate self-signed certificates: - ```bash - cd listener-certs/ - openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout default-listener.key \ - -out default-listener.crt \ - -subj "/CN=localhost" - ``` -- After fixing, restart: `docker compose restart event-gateway` - -## Quick Reference - -### Common Commands - -```bash -# Start services -docker compose up -d - -# Stop services -docker compose down - -# View logs -docker compose logs -f event-gateway - -# Restart event gateway only -docker compose restart event-gateway - -# Rebuild after code changes -make build-gateway-runtime -docker compose up -d --build event-gateway - -# WebBrokerApi Management (Control Plane Mode) -# Create WebBrokerApi -curl -X POST http://localhost:9090/api/management/v0.9/webbroker-apis \ - -u admin:admin \ - -H "Content-Type: application/json" \ - -d @webbroker-config.json - -# List WebBrokerApis -curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis \ - -u admin:admin - -# Get WebBrokerApi by ID -curl -X GET http://localhost:9090/api/management/v0.9/webbroker-apis/websocket-kafka-api-v1-0 \ - -u admin:admin - -# Delete WebBrokerApi -curl -X DELETE http://localhost:9090/api/management/v0.9/webbroker-apis/websocket-kafka-api-v1-0 \ - -u admin:admin - -# Kafka Management -# List Kafka topics -docker exec event-gateway-kafka-1 kafka-topics \ - --bootstrap-server localhost:29092 --list - -# Create a new Kafka topic -docker exec event-gateway-kafka-1 kafka-topics \ - --bootstrap-server localhost:29092 \ - --create --topic my-topic \ - --partitions 3 --replication-factor 1 - -# Consume from topic -docker exec event-gateway-kafka-1 kafka-console-consumer \ - --bootstrap-server localhost:29092 \ - --topic repo-events \ - --from-beginning - -# Produce to topic -docker exec -it event-gateway-kafka-1 kafka-console-producer \ - --bootstrap-server localhost:29092 \ - --topic repo-events -``` - -### Configuration Reference - -**Event Gateway Environment Variables:** - -| Variable | Description | Default | -|----------|-------------|---------| -| `APIP_EGW_KAFKA_BROKERS` | Kafka broker addresses | `kafka:29092` | -| `APIP_EGW_SERVER_WEBSOCKET_PORT` | WebSocket server port | `8081` | -| `APIP_EGW_SERVER_ADMIN_PORT` | Admin API port | `9002` | -| `APIP_EGW_LOGGING_LEVEL` | Log level (debug/info/warn/error) | `info` | -| `APIP_EGW_CONTROLPLANE_ENABLED` | Enable xDS control plane | `true` | -| `APIP_EGW_CONTROLPLANE_XDS_ADDRESS` | xDS server address | `gateway-controller:18001` | - -**Config File Locations:** - -- Runtime config: `gateway-runtime/configs/config.toml` -- Channels config: `gateway-runtime/configs/channels.yaml` -- Controller config: `../gateway/configs/config.toml` (shared with main gateway) - -### Sample WebBrokerApi Configurations - -**Minimal Config (No Policies):** - -```yaml -channels: - - kind: WebBrokerApi - name: simple-ws - version: v1.0 - context: /simple - receiver: - name: ws-receiver - type: websocket - broker-driver: - name: kafka-driver - type: kafka - properties: - bootstrap.servers: kafka:29092 - policies: - on_connection_init: - request: [] - response: [] - on_produce: [] - on_consume: [] - channels: - "/default": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: simple-events - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: simple-events -``` - -**With Authentication:** - -```yaml -channels: - - kind: WebBrokerApi - name: secure-ws - version: v1.0 - context: /secure - receiver: - name: ws-receiver - type: websocket - broker-driver: - name: kafka-driver - type: kafka - properties: - bootstrap.servers: kafka:29092 - policies: - on_connection_init: - request: - - name: api-key-auth - version: v1 - params: - in: header - name: X-API-Key - on_produce: [] - on_consume: [] - channels: - "/secure-channel": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: secure-events - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: secure-events -``` - -**With Multiple Channels:** - -```yaml -channels: - - kind: WebBrokerApi - name: smart-ws - version: v1.0 - context: /smart - receiver: - name: ws-receiver - type: websocket - broker-driver: - name: kafka-driver - type: kafka - properties: - bootstrap.servers: kafka:29092 - policies: - on_connection_init: - request: - - name: api-key-auth - version: v1 - on_produce: [] - on_consume: - - name: set-headers - version: v1 - params: - headers: - X-Gateway: event-gateway - X-Timestamp: "${timestamp}" - channels: - "/users": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: users-topic - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: users-topic - "/orders": - on_produce: - - name: map-topic - version: v1 - params: - mode: produceTo - topic: orders-topic - on_consume: - - name: map-topic - version: v1 - params: - mode: consumeFrom - topic: orders-topic -``` - -### Useful Tools - -**websocat** - WebSocket CLI client: -```bash -# Install -brew install websocat # macOS -cargo install websocat # Linux/Rust - -# Basic usage -websocat --header "X-channel: /channel-name" ws://localhost:8081/ws-kafka - -# With headers -websocat --header "X-API-Key: test" --header "X-channel: /secure-channel" ws://localhost:8081/secure - -# With text protocol -websocat -t --header "X-channel: /channel-name" ws://localhost:8081/ws-kafka -``` - -**wscat** - Alternative WebSocket CLI: -```bash -# Install -npm install -g wscat - -# Connect -wscat -c ws://localhost:8081/ws-kafka -H "X-channel: /channel-name" - -# With headers -wscat -c ws://localhost:8081/secure -H "X-API-Key: test" -H "X-channel: /secure-channel" -``` - -**kcat (kafkacat)** - Kafka CLI tool: -```bash -# Install -brew install kcat # macOS -apt install kafkacat # Linux - -# Consume -kcat -b localhost:9092 -t repo-events -C - -# Produce -echo "test message" | kcat -b localhost:9092 -t repo-events -P -``` - -## Current Spec for WebBroker APIs - -```json -{ - "apiVersion": "gateway.api-platform.wso2.com/v1alpha1", - "kind": "WebBrokerApi", - "metadata": { - "name": "websocket-kafka-api-v1-0" - }, - "spec": { - "displayName": "websocket-kafka-api", - "version": "v1.0", - "context": "/websocket-kafka", - - "receiver": { - "name": "my-websocket-receiver", - "type": "websocket", - "properties": {} - }, - - "brokerDriver": { - "name": "my-kafka-broker", - "type": "kafka", - "properties": { - "bootstrap.servers": "localhost:9092", - "security.protocol": "PLAINTEXT" - } - }, - - "policies": { - "onConnectionInit": { - "request": [ - { - "name": "api-key-auth", - "version": "v1", - "params": { - "in": "header", - "name": "X-API-Key" - } - } - ], - "response": [] - }, - "onProduce": [], - "onConsume": [] - }, - - "channels": { - "/issues": { - "on_connection_init": { - "request": [], - "response": [] - }, - "on_produce": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "produceTo", - "topic": "kafka-repo-issues" - } - } - ], - "on_consume": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "consumeFrom", - "topic": "kafka-repo-issues" - } - } - ] - }, - "/commits": { - "on_produce": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "produceTo", - "topic": "kafka-repo-commits" - } - } - ], - "on_consume": [ - { - "name": "map-topic", - "version": "v1", - "params": { - "mode": "consumeFrom", - "topic": "kafka-repo-commits" - } - } - ] - } - }, - - "deploymentState": "deployed" - } -} -``` - -### Key Changes in New Spec - -1. **Receiver and BrokerDriver Names**: Added `name` field to both `receiver` and `brokerDriver` for instance identification -2. **API-Level Policies**: Renamed `allChannelPolicies` → `policies` for API-level policy enforcement -3. **Channel-Specific Configs**: New `channels` map where each key is a channel name (e.g., `/issues`, `/commits`) -4. **Per-Channel Policies**: Each channel has its own `on_connection_init`, `on_produce`, and `on_consume` policies -5. **Topic Extraction**: Topics no longer in `brokerDriver.properties.topic`; instead extracted from `map-topic` policies with `mode: produceTo` and `mode: consumeFrom` -6. **Channel Routing**: Client selects channel via `X-channel` header during WebSocket handshake -7. **Policy Cascade**: API-level policies execute first, then channel-specific policies - -## Next Steps - -- Review [WebSub documentation](README.md) for comparison -- Explore [policy development](../gateway/README.md) for custom policies -- Check [performance tuning guide](docs/performance.md) for optimization -- Learn about [monitoring and observability](docs/observability.md) From 8e0582d0a978905a5702a5b165e3f545389d4195 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 20:31:14 +0530 Subject: [PATCH 17/20] Fix review comments --- event-gateway/ARCHITECTURE.md | 2 +- .../receiver/websocket/broker_api_connector.go | 2 +- .../gateway-runtime/internal/xdsclient/handler.go | 11 ++++------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/event-gateway/ARCHITECTURE.md b/event-gateway/ARCHITECTURE.md index 9a06a2059..e6be09d43 100644 --- a/event-gateway/ARCHITECTURE.md +++ b/event-gateway/ARCHITECTURE.md @@ -329,7 +329,7 @@ WebBrokerApiBinding { ### WebBrokerApi Flow (Bidirectional WebSocket ↔ Kafka) -**Note:** This diagram shows WebSocket + Kafka as an example. The architecture supports any receiver type (eg: SSE) with any broker driver (eg: MQTT, RabbitMQ) through the plugin system (see [Extensibility](#11-extensibility--plugin-architecture)). +**Note:** This diagram shows WebSocket + Kafka as an example. The architecture supports any receiver type (eg: SSE) with any broker driver (eg: MQTT, RabbitMQ) through the plugin system (see [Extensibility](#12-extensibility--plugin-architecture)). ``` ┌───────────────┐ diff --git a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go index 5986d18d2..56f6e67eb 100644 --- a/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go +++ b/event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go @@ -450,7 +450,7 @@ func (e *WebBrokerApiReceiver) inboundLoop(ctx context.Context, conn *brokerApiC if err := e.brokerDriver.Publish(ctx, targetTopic, processed); err != nil { slog.Error("[6] Failed to publish to Kafka", "connID", conn.connID, "topic", targetTopic, "error", err) } else { - slog.Info("[6] Message successfully published to Kafka", "connID", conn.connID, "topic", targetTopic) + slog.Debug("[6] Message successfully published to Kafka", "connID", conn.connID, "topic", targetTopic) } } } diff --git a/event-gateway/gateway-runtime/internal/xdsclient/handler.go b/event-gateway/gateway-runtime/internal/xdsclient/handler.go index 404578bdb..317807f31 100644 --- a/event-gateway/gateway-runtime/internal/xdsclient/handler.go +++ b/event-gateway/gateway-runtime/internal/xdsclient/handler.go @@ -179,19 +179,16 @@ func (h *Handler) HandleResources(ctx context.Context, resources []*discoveryv3. continue } - slog.Info("DEBUG: Raw JSON from xDS", "json", string(data)) - var ecr EventChannelResource if err := json.Unmarshal(data, &ecr); err != nil { slog.Error("Failed to decode EventChannelConfig", "error", err) continue } - slog.Info("DEBUG: Deserialized EventChannelResource", - "uuid", ecr.UUID, - "name", ecr.Name, - "kind", ecr.Kind, - "brokerDriver", ecr.BrokerDriver) + if ecr.UUID == "" { + slog.Warn("EventChannelConfig resource missing UUID, skipping") + continue + } // Deletion markers are pushed by the controller to work around // a go-control-plane LinearCache limitation for custom type URLs. // Treat them as absent so the diff logic removes the binding. From bc798d2bc144fba0f6dcd7facf14fcbf17472302 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Wed, 13 May 2026 20:54:49 +0530 Subject: [PATCH 18/20] Improve doc --- event-gateway/ARCHITECTURE.md | 289 ++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) diff --git a/event-gateway/ARCHITECTURE.md b/event-gateway/ARCHITECTURE.md index e6be09d43..d218cb438 100644 --- a/event-gateway/ARCHITECTURE.md +++ b/event-gateway/ARCHITECTURE.md @@ -395,6 +395,295 @@ WebBrokerApiBinding { - **Consumer Group:** `{prefix}-ws-{uuid}` ensures per-connection isolation - **Bidirectional Channels:** Inbound (client→Kafka) and Outbound (Kafka→client) Go channels +### WebBrokerApi Connection Architecture (Detailed) + +#### Per-Connection Resources + +Each WebSocket connection creates the following dedicated resources: + +**File:** `event-gateway/gateway-runtime/internal/connectors/receiver/websocket/broker_api_connector.go` + +```go +type brokerApiConnection struct { + connID string // UUID for this connection + ws *websocket.Conn // WebSocket connection + inbound chan *connectors.Message // client → broker (256 buffer) + outbound chan *connectors.Message // broker → client (256 buffer) + kafkaConsumer connectors.Receiver // Dedicated Kafka consumer + cancel context.CancelFunc // Cancel function for cleanup + channelName string // Selected channel from X-channel header + produceChainKey string // Policy chain key for on_produce + consumeChainKey string // Policy chain key for on_consume + produceTopic string // Target Kafka topic for producing + consumeTopic string // Source Kafka topic for consuming +} +``` + +**Created during WebSocket handshake at lines 299-311:** + +```go +connID := uuid.New().String() +conn := &brokerApiConnection{ + connID: connID, + ws: ws, + inbound: make(chan *connectors.Message, 256), // ← PRODUCE Go channel + outbound: make(chan *connectors.Message, 256), // ← CONSUME Go channel + channelName: channelName, // From X-channel header + produceChainKey: produceChainKey, // From channel config + consumeChainKey: consumeChainKey, // From channel config + produceTopic: produceTopic, // From channel config + consumeTopic: consumeTopic, // From channel config +} +``` + +#### Kafka Resource Naming + +**Consumer Group Naming (Per-Connection):** + +```go +// Line 318 +groupID := fmt.Sprintf("%s-ws-%s", e.opts.ConsumerGroupPrefix, connID) +// Example: "event-gateway-ws-a1b2c3d4-e5f6-7890-abcd-ef1234567890" +``` + +**Format:** `{prefix}-ws-{uuid}` +- `prefix` - Configured consumer group prefix (e.g., `event-gateway`) +- `ws` - Indicates WebSocket receiver type +- `uuid` - Unique connection ID + +**Producer (Shared Globally):** +- **One shared Publisher** instance for ALL WebSocket connections +- Created at gateway startup via `NewBrokerDriver()` +- No per-connection naming—all connections use the same producer via `e.brokerDriver.Publish()` + +#### Why Shared Producer vs. Per-Connection Consumer? + +**Consumers MUST be per-connection (stateful):** +- Each consumer maintains unique consumer group membership +- Each tracks its own offset position in topics independently +- Each receives ALL messages from subscribed topics (not distributed) +- Required for per-connection message isolation + +**Producers SHOULD be shared (stateless):** +- Topic specified per `Publish()` call—no per-connection state +- franz-go's `kgo.Client` is thread-safe for concurrent use +- Internal batching: messages from multiple connections batched into single network requests +- Resource efficiency: 1 producer = 3 TCP connections; 1000 producers = 3000+ TCP connections +- Memory efficiency: single set of send buffers (~100KB) vs. per-connection buffers +- Reduced broker load: Kafka handles 1 producer instead of 1000 + +#### Goroutine Architecture + +Each WebSocket connection spawns **three goroutines** (line 350-352): + +```go +go e.readLoop(ctx, conn) // WebSocket → inbound channel +go e.inboundLoop(ctx, conn) // inbound channel → Kafka +go e.outboundLoop(ctx, conn) // outbound channel → WebSocket +``` + +**Plus one additional thread:** +- **Kafka consumer goroutine** (internal to franz-go library) that reads from Kafka and calls the callback + +**Total per connection:** 3 goroutines + 1 Kafka consumer thread + +#### Complete Handshake Sequence + +**1. Extract & Validate X-channel Header (lines 143-183)** +```go +xChannelHeader := r.Header.Get("X-channel") +// Validate channel exists in API definition +``` + +**2. Apply onConnectionInit.request Policies (lines 203-230)** +```go +processed, shortCircuited, err := e.processor.ProcessConnectionInitRequest(...) +if shortCircuited { + http.Error(w, "connection rejected", http.StatusForbidden) + return +} +``` + +**3. Upgrade HTTP → WebSocket (lines 232-240)** +```go +ws, err := upgrader.Upgrade(w, r, nil) +``` + +**4. Apply onConnectionInit.response Policies (lines 244-252)** +```go +e.processor.ProcessConnectionInitResponse(...) +``` + +**5. Extract Channel Configuration (lines 254-282)** +```go +// Get policy chain keys for this channel +produceChainKey = channelChainsMap[channelName]["ProduceKey"] +consumeChainKey = channelChainsMap[channelName]["ConsumeKey"] + +// Get topic mappings +produceTopic = channelTopicsMap[channelName]["produceTo"] +consumeTopic = channelTopicsMap[channelName]["consumeFrom"] +``` + +**6. Create Per-Connection Resources (lines 299-311)** +```go +conn := &brokerApiConnection{ + inbound: make(chan *connectors.Message, 256), + outbound: make(chan *connectors.Message, 256), + // ... other fields +} +``` + +**7. Create & Start Kafka Consumer (lines 318-335)** +```go +consumer, err := e.brokerDriver.Subscribe(groupID, channelTopics, func(ctx context.Context, msg *connectors.Message) error { + // Kafka message → outbound Go channel + conn.outbound <- msg + return nil +}) +consumer.Start(ctx) +``` + +**8. Start Goroutines (lines 350-352)** +```go +go e.readLoop(ctx, conn) +go e.inboundLoop(ctx, conn) +go e.outboundLoop(ctx, conn) +``` + +#### Message Production Flow (Client → Kafka) + +**Path:** `WebSocket client` → `readLoop` → `inbound channel` → `inboundLoop` → `Kafka` + +**Step 1: Read from WebSocket (readLoop, lines 356-394)** +```go +msgType, data, err := conn.ws.ReadMessage() +msg := &connectors.Message{Value: data} +conn.inbound <- msg // Push to inbound Go channel +``` + +**Step 2: Apply onProduce Policies (inboundLoop, lines 403-425)** +```go +processed, shortCircuited, err := e.processor.ProcessByChainKey( + ctx, + e.channel.Name, + conn.produceChainKey, // Channel-specific policy chain + msg +) +``` + +**Step 3: Publish to Kafka (inboundLoop, lines 427-449)** +```go +targetTopic := conn.produceTopic // From channel config +e.brokerDriver.Publish(ctx, targetTopic, processed) +``` + +#### Message Consumption Flow (Kafka → Client) + +**Path:** `Kafka` → `consumer callback` → `outbound channel` → `outboundLoop` → `WebSocket client` + +**Step 1: Kafka Consumer Callback (lines 319-331)** +```go +// Runs in Kafka consumer goroutine (franz-go) +consumer, err := e.brokerDriver.Subscribe(groupID, channelTopics, func(ctx context.Context, msg *connectors.Message) error { + conn.outbound <- msg // Push to outbound Go channel + return nil +}) +``` + +**Step 2: Apply onConsume Policies (outboundLoop, lines 461-479)** +```go +msg := <-conn.outbound // Read from outbound Go channel +processed, shortCircuited, err := e.processor.ProcessByChainKey( + ctx, + e.channel.Name, + conn.consumeChainKey, // Channel-specific policy chain + msg +) +``` + +**Step 3: Write to WebSocket (outboundLoop, lines 487-492)** +```go +conn.ws.WriteMessage(websocket.BinaryMessage, processed.Value) +``` + +#### Connection Teardown + +**Triggered by:** +- Client disconnects +- WebSocket read/write error +- Context cancellation + +**Cleanup sequence (closeConnection, lines 502-528):** +1. Cancel context → stops all goroutines +2. Stop Kafka consumer: `conn.kafkaConsumer.Stop()` +3. Close Go channels: `close(conn.inbound)`, `close(conn.outbound)` +4. Close WebSocket: `conn.ws.Close()` +5. Deregister connection: `delete(e.connections, conn.connID)` + +#### Architecture Diagram + +``` +┌──────────────────┐ +│ WebSocket Client │ +└────────┬─────────┘ + │ X-channel: prices + │ Handshake + ┌────▼────────────────────────────────────────────────────────┐ + │ WebBrokerApiReceiver.handleUpgrade() │ + │ 1. Validate X-channel 2. Auth policies 3. Upgrade │ + └────────────────────────┬───────────────────────────────────┘ + │ Create brokerApiConnection + ┌────────────────────────▼───────────────────────────────────┐ + │ brokerApiConnection │ + │ • connID: "uuid" │ + │ • ws: *websocket.Conn │ + │ • inbound: chan *Message (256) ← Client messages │ + │ • outbound: chan *Message (256) ← Kafka messages │ + │ • kafkaConsumer: group="egw-ws-uuid" │ + │ • channelName: "prices" │ + │ • produceTopic: "stock.prices" │ + │ • consumeTopic: "dummy.prices" │ + └─────────────────────────┬──────────────────────────────────┘ + │ + ┌────────────────────┼────────────────────┐ + │ │ │ + ┌────▼──────┐ ┌────────▼───────┐ ┌──────▼─────────┐ + │ readLoop │ │ inboundLoop │ │ outboundLoop │ + │ goroutine │ │ goroutine │ │ goroutine │ + └─────┬─────┘ └────────┬───────┘ └──────┬─────────┘ + │ │ │ + │ ws.ReadMessage() │ │ ws.WriteMessage() + │ │ │ + ▼ ▼ ▲ + inbound ──────────→ Policies ──→ Kafka │ + channel onProduce Publish │ + │ │ + │ │ + Kafka │ + Consumer Policies + (franz-go) onConsume + callback │ + │ │ + └─────→ outbound + channel + + ┌─────────────────────────────────────────────────────────────┐ + │ Shared Resources │ + │ • KafkaBrokerDriver (one instance globally) │ + │ - Publisher: shared by ALL connections │ + │ - Admin client: topic management │ + └─────────────────────────────────────────────────────────────┘ +``` + +**Key Takeaways:** +- ✅ **2 Go channels** per connection (256-message buffers each) +- ✅ **3 goroutines** per connection (readLoop, inboundLoop, outboundLoop) +- ✅ **1 dedicated Kafka consumer** per connection (unique consumer group) +- ✅ **1 shared Kafka producer** for all connections (global) +- ✅ **Per-channel policy chains** (produceChainKey, consumeChainKey) +- ✅ **Automatic cleanup** on connection close (goroutines, consumer, channels) + --- ## 6. Policy Engine Integration From 6af5c99d78243794d7ba4e9db68dddc78d40df97 Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Thu, 14 May 2026 00:58:54 +0530 Subject: [PATCH 19/20] Improve code --- event-gateway/ARCHITECTURE.md | 2 +- event-gateway/gateway-runtime/Dockerfile | 1 - event-gateway/gateway-runtime/Makefile | 3 --- gateway/gateway-runtime/policy-engine/pkg/engine/types.go | 4 ++-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/event-gateway/ARCHITECTURE.md b/event-gateway/ARCHITECTURE.md index d218cb438..859f7b6b4 100644 --- a/event-gateway/ARCHITECTURE.md +++ b/event-gateway/ARCHITECTURE.md @@ -359,7 +359,7 @@ WebBrokerApiBinding { │ Hub.ProcessByChainKey(produceChainKey) │ │ │ - API-level on_produce │ │ │ - Channel-level on_produce (/issues) │ │ -│ - map-topic policy sets kafka.topic │ │ +│ - map-topic policy sets target topic │ │ └──────┬───────────────────────────────────┘ │ │ │ ▼ │ diff --git a/event-gateway/gateway-runtime/Dockerfile b/event-gateway/gateway-runtime/Dockerfile index a0ae9b25e..50a716d40 100644 --- a/event-gateway/gateway-runtime/Dockerfile +++ b/event-gateway/gateway-runtime/Dockerfile @@ -86,7 +86,6 @@ COPY --from=sdk-core . /api-platform/sdk/core COPY --from=common . /api-platform/common COPY --from=analytics . /api-platform/gateway/system-policies/analytics COPY --from=policy-engine . /api-platform/gateway/gateway-runtime/policy-engine -COPY --from=policies . /api-platform/event-gateway/sample-policies COPY --from=target build.yaml /api-platform/event-gateway/build.yaml RUN mkdir -p /api-platform/output diff --git a/event-gateway/gateway-runtime/Makefile b/event-gateway/gateway-runtime/Makefile index 87157d4f6..e98eb8e30 100644 --- a/event-gateway/gateway-runtime/Makefile +++ b/event-gateway/gateway-runtime/Makefile @@ -89,7 +89,6 @@ build-docker: ## Build Docker image --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ - --build-context policies=../sample-policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ @@ -110,7 +109,6 @@ build-debug: ## Build Docker image with Delve remote debugger (port 2345) --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ - --build-context policies=../sample-policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ @@ -131,7 +129,6 @@ build-and-push-multiarch: ## Build and push Docker image for multiple architectu --build-context policy-engine=../../gateway/gateway-runtime/policy-engine \ --build-context analytics=../../gateway/system-policies/analytics \ --build-context gateway-builder=../gateway-builder \ - --build-context policies=../sample-policies \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ diff --git a/gateway/gateway-runtime/policy-engine/pkg/engine/types.go b/gateway/gateway-runtime/policy-engine/pkg/engine/types.go index f58f5b5dd..471efd411 100644 --- a/gateway/gateway-runtime/policy-engine/pkg/engine/types.go +++ b/gateway/gateway-runtime/policy-engine/pkg/engine/types.go @@ -41,7 +41,7 @@ type RequestBodyResult struct { HeadersToSet map[string]string HeadersToRemove []string Body []byte - Topic string // Kafka topic for publish (set by policies like map-topic) + Topic string // Broker topic for publish (set by policies like map-topic) ShortCircuited bool ImmediateResponse *ImmediateResponseResult TotalDuration time.Duration @@ -137,7 +137,7 @@ func mapRequestBodyResult(r *executor.RequestExecutionResult) *RequestBodyResult // Extract topic from metadata if set by policies (e.g., map-topic policy) if r.Metadata != nil { - if topic, ok := r.Metadata["kafka.topic"].(string); ok && topic != "" { + if topic, ok := r.Metadata["topic"].(string); ok && topic != "" { res.Topic = topic } } From 09b9724fa8172348a18a4323119ffab0f282671d Mon Sep 17 00:00:00 2001 From: senthuran16 Date: Thu, 14 May 2026 02:18:40 +0530 Subject: [PATCH 20/20] Run make generate after pulling the main branch --- docs/rest-apis/gateway/schemas.md | 252 +++++---- .../gateway/websub-api-management.md | 28 +- .../pkg/api/management/generated.go | 486 +++++++++--------- 3 files changed, 424 insertions(+), 342 deletions(-) diff --git a/docs/rest-apis/gateway/schemas.md b/docs/rest-apis/gateway/schemas.md index 01f4e7bf4..c1bc42b32 100644 --- a/docs/rest-apis/gateway/schemas.md +++ b/docs/rest-apis/gateway/schemas.md @@ -648,68 +648,82 @@ xor "main": "api.example.com", "sandbox": "sandbox-api.example.com" }, - "policies": { - "on_subscription": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_unsubscription": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_message_received": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_message_delivery": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ] + "allChannels": { + "on_subscription": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_unsubscription": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_message_received": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_message_delivery": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } }, "channels": { "property1": { - "policies": { - "on_subscription": [ + "on_subscription": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_unsubscription": [ + ] + }, + "on_unsubscription": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_message_received": [ + ] + }, + "on_message_received": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_message_delivery": [ + ] + }, + "on_message_delivery": { + "policies": [ { "name": "cors", "version": "v1", @@ -720,32 +734,38 @@ xor } }, "property2": { - "policies": { - "on_subscription": [ + "on_subscription": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_unsubscription": [ + ] + }, + "on_unsubscription": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_message_received": [ + ] + }, + "on_message_received": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_message_delivery": [ + ] + }, + "on_message_delivery": { + "policies": [ { "name": "cors", "version": "v1", @@ -771,7 +791,7 @@ xor |vhosts|object|false|none|Custom virtual hosts/domains for the API| |» main|string|true|none|Custom virtual host/domain for production traffic| |» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| -|policies|[WebSubAllChannelPolicies](#schemawebsuballchannelpolicies)|false|none|Policies applied to all channels, organized by event type.| +|allChannels|[WebSubAllChannelPolicies](#schemawebsuballchannelpolicies)|false|none|Policies applied to all channels, organized by event type.| |channels|object|false|none|Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel.| |» **additionalProperties**|[WebSubChannel](#schemawebsubchannel)|false|none|A single channel definition with optional per-channel policy overrides.| |deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment.| @@ -792,32 +812,38 @@ xor ```json { - "policies": { - "on_subscription": [ + "on_subscription": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_unsubscription": [ + ] + }, + "on_unsubscription": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_message_received": [ + ] + }, + "on_message_received": { + "policies": [ { "name": "cors", "version": "v1", "executionCondition": "request.metadata[authenticated] != true", "params": {} } - ], - "on_message_delivery": [ + ] + }, + "on_message_delivery": { + "policies": [ { "name": "cors", "version": "v1", @@ -836,42 +862,21 @@ A single channel definition with optional per-channel policy overrides. |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|policies|[WebSubChannelPolicies](#schemawebsubchannelpolicies)|false|none|Policies applied to a specific channel, organized by event type.| +|on_subscription|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|on_unsubscription|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|on_message_received|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|on_message_delivery|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.| -

WebSubAllChannelPolicies

+

WebSubEventPolicies

- - - - + + + + ```json { - "on_subscription": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_unsubscription": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_message_received": [ - { - "name": "cors", - "version": "v1", - "executionCondition": "request.metadata[authenticated] != true", - "params": {} - } - ], - "on_message_delivery": [ + "policies": [ { "name": "cors", "version": "v1", @@ -883,16 +888,77 @@ A single channel definition with optional per-channel policy overrides. ``` +Policies for a single event type. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies applied for this event type.| + +

WebSubAllChannelPolicies

+ + + + + + +```json +{ + "on_subscription": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_unsubscription": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_message_received": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + }, + "on_message_delivery": { + "policies": [ + { + "name": "cors", + "version": "v1", + "executionCondition": "request.metadata[authenticated] != true", + "params": {} + } + ] + } +} + +``` + Policies applied to all channels, organized by event type. ### Properties |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|on_subscription|[[Policy](#schemapolicy)]|false|none|Policies applied when a client subscribes to a channel (e.g., api-key-auth)| -|on_unsubscription|[[Policy](#schemapolicy)]|false|none|Policies applied when a client unsubscribes from a channel (e.g., api-key-auth)| -|on_message_received|[[Policy](#schemapolicy)]|false|none|Policies applied when a message is received from the publisher via webhook (e.g., hmac-signature-validation)| -|on_message_delivery|[[Policy](#schemapolicy)]|false|none|Policies applied when delivering a message to a subscriber callback URL (e.g., hmac-sign-messages)| +|on_subscription|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|on_unsubscription|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|on_message_received|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|on_message_delivery|[WebSubEventPolicies](#schemawebsubeventpolicies)|false|none|Policies for a single event type.|

WebSubChannelPolicies

diff --git a/docs/rest-apis/gateway/websub-api-management.md b/docs/rest-apis/gateway/websub-api-management.md index d1a227e0a..91cf39fcb 100644 --- a/docs/rest-apis/gateway/websub-api-management.md +++ b/docs/rest-apis/gateway/websub-api-management.md @@ -229,22 +229,22 @@ Status Code **200** |»»»» vhosts|object|false|none|Custom virtual hosts/domains for the API| |»»»»» main|string|true|none|Custom virtual host/domain for production traffic| |»»»»» sandbox|string|false|none|Custom virtual host/domain for sandbox traffic| -|»»»» policies|[WebSubAllChannelPolicies](schemas.md#schemawebsuballchannelpolicies)|false|none|Policies applied to all channels, organized by event type.| -|»»»»» on_subscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client subscribes to a channel (e.g., api-key-auth)| -|»»»»»» name|string|true|none|Name of the policy| -|»»»»»» version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected. The Gateway Controller resolves the major version to the single matching full version installed in the gateway image.| -|»»»»»» executionCondition|string|false|none|Expression controlling conditional execution of the policy| -|»»»»»» params|object|false|none|Arbitrary parameters for the policy (free-form key/value structure)| -|»»»»» on_unsubscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client unsubscribes from a channel (e.g., api-key-auth)| -|»»»»» on_message_received|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a message is received from the publisher via webhook (e.g., hmac-signature-validation)| -|»»»»» on_message_delivery|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when delivering a message to a subscriber callback URL (e.g., hmac-sign-messages)| +|»»»» allChannels|[WebSubAllChannelPolicies](schemas.md#schemawebsuballchannelpolicies)|false|none|Policies applied to all channels, organized by event type.| +|»»»»» on_subscription|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|»»»»»» policies|[[Policy](schemas.md#schemapolicy)]|false|none|List of policies applied for this event type.| +|»»»»»»» name|string|true|none|Name of the policy| +|»»»»»»» version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected. The Gateway Controller resolves the major version to the single matching full version installed in the gateway image.| +|»»»»»»» executionCondition|string|false|none|Expression controlling conditional execution of the policy| +|»»»»»»» params|object|false|none|Arbitrary parameters for the policy (free-form key/value structure)| +|»»»»» on_unsubscription|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|»»»»» on_message_received|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|»»»»» on_message_delivery|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| |»»»» channels|object|false|none|Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel.| |»»»»» **additionalProperties**|[WebSubChannel](schemas.md#schemawebsubchannel)|false|none|A single channel definition with optional per-channel policy overrides.| -|»»»»»» policies|[WebSubChannelPolicies](schemas.md#schemawebsubchannelpolicies)|false|none|Policies applied to a specific channel, organized by event type.| -|»»»»»»» on_subscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client subscribes to this channel (e.g., rbac)| -|»»»»»»» on_unsubscription|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a client unsubscribes from this channel| -|»»»»»»» on_message_received|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when a message is received for this channel| -|»»»»»»» on_message_delivery|[[Policy](schemas.md#schemapolicy)]|false|none|Policies applied when delivering a message for this channel| +|»»»»»» on_subscription|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|»»»»»» on_unsubscription|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|»»»»»» on_message_received|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| +|»»»»»» on_message_delivery|[WebSubEventPolicies](schemas.md#schemawebsubeventpolicies)|false|none|Policies for a single event type.| |»»»» deploymentState|string|false|none|Desired deployment state - 'deployed' (default) or 'undeployed'. When set to 'undeployed', the API is removed from router traffic but configuration, API keys, and policies are preserved for potential redeployment.| *and* diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index 7a5d747dc..3d71483c0 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -4817,241 +4817,257 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file