From 45ed1d756e8d65b8c1219ffa1e739e769386a3ab Mon Sep 17 00:00:00 2001 From: Tharindu Dharmarathna Date: Mon, 13 Jul 2026 10:52:00 +0530 Subject: [PATCH] abstracting event-gateway from gateway-controller --- cli/src/go.mod | 42 +- cli/src/go.sum | 102 +- event-gateway/Makefile | 51 +- event-gateway/gateway-controller/Dockerfile | 147 ++ event-gateway/gateway-controller/Makefile | 137 ++ .../api/eventgateway-openapi.yaml | 2019 +++++++++++++++++ .../gateway-controller/cmd/controller/main.go | 789 +++++++ .../cmd/controller/runtime_bootstrap.go | 194 ++ event-gateway/gateway-controller/go.mod | 88 + event-gateway/gateway-controller/go.sum | 218 ++ .../gateway-controller/oapi-codegen.yaml | 9 + .../pkg/api/eventgateway/generated.go | 1917 ++++++++++++++++ .../pkg/config/eventgateway_config.go | 98 + .../pkg/config/validator.go | 260 +++ ...bsub_topic_registration_test_staged.go.txt | 0 .../eventlistener/webhook_secret_handler.go | 102 +- .../gateway-controller/pkg/handler/helpers.go | 65 + .../pkg/handler/resource_response.go | 82 + .../gateway-controller/pkg/handler/server.go | 177 ++ .../pkg/handler}/webbroker_api_handler.go | 87 +- .../pkg/handler}/websub_api_handler.go | 183 +- .../pkg/hubtopic/deregistrar.go | 102 + .../pkg/kindsupport/kindsupport.go | 261 +++ .../policyhooks}/event_channel_translator.go | 153 +- .../pkg/translator/hooks.go | 467 ++++ .../webhooksecretservice}/webhook_secret.go | 17 +- .../pkg/webhooksecretxds/snapshot.go | 10 +- gateway/gateway-controller/Dockerfile | 3 +- gateway/gateway-controller/Makefile | 7 +- .../api/management-openapi.yaml | 1694 +------------- .../gateway-controller/cmd/controller/main.go | 27 +- gateway/gateway-controller/go.mod | 30 +- gateway/gateway-controller/go.sum | 82 +- .../pkg/api/handlers/handlerkit/handlerkit.go | 219 ++ .../pkg/api/handlers/handlers.go | 142 +- .../pkg/api/handlers/handlers_test.go | 45 - .../pkg/api/handlers/resource_response.go | 20 - .../pkg/api/management/generated.go | 1897 +++------------- .../pkg/config/api_validator.go | 134 +- .../pkg/config/api_validator_test.go | 203 -- .../gateway-controller/pkg/config/config.go | 50 - .../pkg/config/config_test.go | 35 - .../gateway-controller/pkg/config/parser.go | 15 - .../pkg/config/validator_test.go | 97 +- .../pkg/controlplane/client.go | 14 +- .../pkg/eventlistener/listener.go | 34 +- .../pkg/eventlistener/listener_test.go | 9 - .../pkg/models/stored_config.go | 12 - .../pkg/models/stored_config_test.go | 15 - .../gateway-controller/pkg/policy/builder.go | 94 +- .../pkg/policyxds/combined_cache.go | 5 +- .../pkg/policyxds/server.go | 14 +- .../pkg/policyxds/snapshot.go | 43 +- .../pkg/service/restapi/service.go | 69 +- .../gateway-controller/pkg/storage/memory.go | 50 +- .../pkg/storage/sql_store.go | 32 +- .../pkg/utils/api_deployment.go | 223 +- .../pkg/utils/api_deployment_test.go | 161 +- .../gateway-controller/pkg/utils/api_key.go | 32 +- .../gateway-controller/pkg/utils/helpers.go | 2 - .../utils/llm_provider_transformer_test.go | 5 - .../pkg/xds/eventgateway_hooks.go | 141 ++ .../gateway-controller/pkg/xds/translator.go | 583 +---- .../pkg/xds/translator_test.go | 347 --- .../tests/integration/storage_test.go | 36 - gateway/it/go.mod | 15 +- gateway/it/go.sum | 15 + go.work | 5 +- go.work.sum | 60 +- platform-api/go.mod | 4 +- platform-api/go.sum | 4 + 71 files changed, 8488 insertions(+), 6013 deletions(-) create mode 100644 event-gateway/gateway-controller/Dockerfile create mode 100644 event-gateway/gateway-controller/Makefile create mode 100644 event-gateway/gateway-controller/api/eventgateway-openapi.yaml create mode 100644 event-gateway/gateway-controller/cmd/controller/main.go create mode 100644 event-gateway/gateway-controller/cmd/controller/runtime_bootstrap.go create mode 100644 event-gateway/gateway-controller/go.mod create mode 100644 event-gateway/gateway-controller/go.sum create mode 100644 event-gateway/gateway-controller/oapi-codegen.yaml create mode 100644 event-gateway/gateway-controller/pkg/api/eventgateway/generated.go create mode 100644 event-gateway/gateway-controller/pkg/config/eventgateway_config.go create mode 100644 event-gateway/gateway-controller/pkg/config/validator.go rename gateway/gateway-controller/pkg/utils/websub_topic_registration_test.go => event-gateway/gateway-controller/pkg/config/websub_topic_registration_test_staged.go.txt (100%) rename gateway/gateway-controller/pkg/eventlistener/webhook_secret_processor.go => event-gateway/gateway-controller/pkg/eventlistener/webhook_secret_handler.go (54%) create mode 100644 event-gateway/gateway-controller/pkg/handler/helpers.go create mode 100644 event-gateway/gateway-controller/pkg/handler/resource_response.go create mode 100644 event-gateway/gateway-controller/pkg/handler/server.go rename {gateway/gateway-controller/pkg/api/handlers => event-gateway/gateway-controller/pkg/handler}/webbroker_api_handler.go (69%) rename {gateway/gateway-controller/pkg/api/handlers => event-gateway/gateway-controller/pkg/handler}/websub_api_handler.go (63%) create mode 100644 event-gateway/gateway-controller/pkg/hubtopic/deregistrar.go create mode 100644 event-gateway/gateway-controller/pkg/kindsupport/kindsupport.go rename {gateway/gateway-controller/pkg/policyxds => event-gateway/gateway-controller/pkg/policyhooks}/event_channel_translator.go (55%) create mode 100644 event-gateway/gateway-controller/pkg/translator/hooks.go rename {gateway/gateway-controller/pkg/utils => event-gateway/gateway-controller/pkg/webhooksecretservice}/webhook_secret.go (92%) rename {gateway => event-gateway}/gateway-controller/pkg/webhooksecretxds/snapshot.go (90%) create mode 100644 gateway/gateway-controller/pkg/api/handlers/handlerkit/handlerkit.go create mode 100644 gateway/gateway-controller/pkg/xds/eventgateway_hooks.go diff --git a/cli/src/go.mod b/cli/src/go.mod index e4a2b304f7..62fc99134b 100644 --- a/cli/src/go.mod +++ b/cli/src/go.mod @@ -5,57 +5,29 @@ go 1.26.5 require ( github.com/spf13/cobra v1.10.1 github.com/wso2/api-platform/gateway/gateway-controller v1.0.0 - golang.org/x/term v0.40.0 - golang.org/x/text v0.35.0 + golang.org/x/term v0.43.0 + golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.14.2 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect - github.com/gin-gonic/gin v1.11.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.29.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/goccy/go-yaml v1.19.1 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/oapi-codegen/runtime v1.1.2 // indirect + github.com/oapi-codegen/runtime v1.5.0 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.57.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.1 // indirect github.com/woodsbury/decimal128 v1.3.0 // indirect - go.uber.org/mock v0.6.0 // 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/sys v0.41.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect + golang.org/x/sys v0.44.0 // indirect ) replace github.com/wso2/api-platform/gateway/gateway-controller => ../../gateway/gateway-controller diff --git a/cli/src/go.sum b/cli/src/go.sum index 458d9e6d3a..69270c2570 100644 --- a/cli/src/go.sum +++ b/cli/src/go.sum @@ -2,93 +2,46 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMz github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= -github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= -github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= -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/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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk= -github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4= +github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -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/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE= -github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= -github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -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/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= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= -github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= +github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= +github.com/oapi-codegen/runtime v1.5.0 h1:aiil4QnH+eiWYSO60eaYZ4aur7sJH3rz6BvT5EBFnxc= +github.com/oapi-codegen/runtime v1.5.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= 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/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= 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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= -github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= -github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= 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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -99,44 +52,21 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= -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/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= -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/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -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= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/Makefile b/event-gateway/Makefile index 98a1930c6f..4e8e20cbbb 100644 --- a/event-gateway/Makefile +++ b/event-gateway/Makefile @@ -69,30 +69,9 @@ help: ## Show this help message build: build-event-gateway-controller build-gateway-runtime build-webhook-listener ## Build all event gateway components .PHONY: build-event-gateway-controller -# TODO: drop the manual `policies` build-context (and event-gateway/default-policies/) -# once gateway-builder is integrated into the event-gateway build pipeline. build-event-gateway-controller: ## Build event-gateway-controller Docker image @echo "Building event-gateway-controller Docker image ($(VERSION))..." - @mkdir -p ../gateway/gateway-controller/target - @cp ../LICENSE ../gateway/gateway-controller/target/ - @cd ../gateway/gateway-controller && \ - docker buildx build -f Dockerfile \ - --build-context sdk=../../sdk \ - --build-context sdk-core=../../sdk/core \ - --build-context common=../../common \ - --build-context httpkit=../../httpkit \ - --build-context build-manifest=.. \ - --build-context policies=../../event-gateway/default-policies \ - --build-context target=target \ - --build-arg VERSION=$(VERSION) \ - --build-arg FUNCTIONALITY_TYPE=event \ - --build-arg GIT_COMMIT=$$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") \ - --target production \ - -t $(EVENT_GATEWAY_CONTROLLER_IMAGE):$(VERSION) \ - -t $(EVENT_GATEWAY_CONTROLLER_IMAGE):latest \ - --load \ - . - @rm -rf ../gateway/gateway-controller/target + $(MAKE) -C gateway-controller build VERSION=$(VERSION) .PHONY: build-gateway-runtime build-gateway-runtime: ## Build event-gateway-runtime Docker image @@ -109,31 +88,9 @@ build-webhook-listener: ## Build webhook-listener Docker image build-and-push-multiarch: build-and-push-multiarch-event-gateway-controller build-and-push-multiarch-gateway-runtime ## Build and push all event gateway components for multiple architectures .PHONY: build-and-push-multiarch-event-gateway-controller -# TODO: drop the manual `policies` build-context (and event-gateway/default-policies/) -# once gateway-builder is integrated into the event-gateway build pipeline. build-and-push-multiarch-event-gateway-controller: ## Build and push event-gateway-controller Docker image for multiple architectures @echo "Building and pushing multi-arch event-gateway-controller Docker image ($(VERSION))..." - @mkdir -p ../gateway/gateway-controller/target - @cp ../LICENSE ../gateway/gateway-controller/target/ - @cd ../gateway/gateway-controller && \ - docker buildx build -f Dockerfile \ - --build-context sdk=../../sdk \ - --build-context sdk-core=../../sdk/core \ - --build-context common=../../common \ - --build-context httpkit=../../httpkit \ - --build-context build-manifest=.. \ - --build-context policies=../../event-gateway/default-policies \ - --build-context target=target \ - --platform linux/amd64,linux/arm64 \ - --build-arg VERSION=$(VERSION) \ - --build-arg FUNCTIONALITY_TYPE=event \ - --build-arg GIT_COMMIT=$$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") \ - --target production \ - -t $(EVENT_GATEWAY_CONTROLLER_IMAGE):$(VERSION) \ - -t $(EVENT_GATEWAY_CONTROLLER_IMAGE):latest \ - --push \ - . - @rm -rf ../gateway/gateway-controller/target + $(MAKE) -C gateway-controller build-and-push-multiarch VERSION=$(VERSION) .PHONY: build-and-push-multiarch-gateway-runtime build-and-push-multiarch-gateway-runtime: ## Build and push event-gateway-runtime Docker image for multiple architectures @@ -151,7 +108,7 @@ test: test-event-gateway-controller test-gateway-runtime ## Run all tests .PHONY: test-event-gateway-controller test-event-gateway-controller: ## Test event-gateway-controller - $(MAKE) -C ../gateway/gateway-controller test + $(MAKE) -C gateway-controller test .PHONY: test-gateway-runtime test-gateway-runtime: ## Test event-gateway-runtime @@ -202,6 +159,6 @@ version-get-release: ## Get release version (strips SNAPSHOT suffix) # Clean Targets .PHONY: clean clean: ## Clean all build artifacts - $(MAKE) -C ../gateway/gateway-controller clean + $(MAKE) -C gateway-controller clean $(MAKE) -C gateway-runtime clean $(MAKE) -C webhook-listener clean diff --git a/event-gateway/gateway-controller/Dockerfile b/event-gateway/gateway-controller/Dockerfile new file mode 100644 index 0000000000..1613bfcb8e --- /dev/null +++ b/event-gateway/gateway-controller/Dockerfile @@ -0,0 +1,147 @@ +# Stage 1: Build the Go application +# Using Debian-based image for better cross-compilation support +FROM --platform=$BUILDPLATFORM golang:1.26.5-bookworm AS builder +ARG TARGETARCH +ARG VERSION=0.0.1-SNAPSHOT +ARG GIT_COMMIT=unknown + +# Coverage build argument - set to "true" for coverage-instrumented builds +ARG ENABLE_COVERAGE=false + +# Debug build argument - set to "true" for debug builds with dlv support (disables optimizations) +ARG ENABLE_DEBUG=false + +WORKDIR /build + +# Install build dependencies for SQLite (CGO) and cross-compilation toolchains +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libc6-dev \ + libsqlite3-dev \ + && if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get install -y --no-install-recommends gcc-x86-64-linux-gnu libc6-dev-amd64-cross; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross; \ + fi + +# Copy go mod files for dependencies (needed for go.mod replace directives) +# Copying only the mod files first ensures that the go mod download cache +# is only invalidated when dependencies change, not when source code changes. +COPY --from=sdk-core go.mod /sdk/core/ +COPY --from=common go.mod go.sum /common/ +COPY --from=httpkit go.mod go.sum /httpkit/ +COPY --from=gateway-controller go.mod go.sum /gateway/gateway-controller/ + +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY --from=sdk-core . /sdk/core +COPY --from=common . /common +COPY --from=httpkit . /httpkit +COPY --from=gateway-controller . /gateway/gateway-controller +COPY . ./ + +# Build with CGO (required for SQLite), set CC for cross-compilation, add -cover/-gcflags if enabled +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + if [ "$TARGETARCH" = "amd64" ]; then \ + export CC=x86_64-linux-gnu-gcc; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + export CC=aarch64-linux-gnu-gcc; \ + else \ + export CC=gcc; \ + fi && \ + if [ "$ENABLE_COVERAGE" = "true" ]; then \ + COVER_FLAG="-cover"; \ + else \ + COVER_FLAG=""; \ + fi && \ + export CGO_ENABLED=1 && \ + export GOOS=linux && \ + export GOARCH=${TARGETARCH} && \ + BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) && \ + VERSION_PKG=github.com/wso2/api-platform/gateway/gateway-controller/pkg/version && \ + LDFLAGS="-X ${VERSION_PKG}.Version=${VERSION} -X ${VERSION_PKG}.FunctionalityType=event -X ${VERSION_PKG}.GitCommit=${GIT_COMMIT} -X ${VERSION_PKG}.BuildDate=${BUILD_DATE}" && \ + if [ "$ENABLE_DEBUG" = "true" ]; then \ + go build $COVER_FLAG \ + -gcflags "all=-N -l" \ + -ldflags "$LDFLAGS" \ + -o controller ./cmd/controller; \ + else \ + go build $COVER_FLAG \ + -ldflags "$LDFLAGS" \ + -o controller ./cmd/controller; \ + fi + +# Stage: debug (select with --target debug; NOT part of the default build) +FROM golang:1.26.5-bookworm AS debug +WORKDIR /app + +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go install github.com/go-delve/delve/cmd/dlv@v1.26.0 + +COPY --from=builder /build/controller . +COPY --from=policies . /app/default-policies +COPY --from=target LICENSE /app/LICENSE +COPY --from=gateway-controller lua /app/lua +COPY --from=gateway-controller default-llm-provider-templates /app/default-llm-provider-templates + +RUN mkdir -p /app/data + +EXPOSE 2345 9090 18000 + +ENTRYPOINT ["/go/bin/dlv", "exec", "/app/controller", \ + "--listen=:2345", "--headless=true", \ + "--api-version=2", "--accept-multiclient", "--"] + +# Stage 2: Runtime image (Ubuntu LTS for glibc compatibility and newer runtime packages) +FROM ubuntu:24.04 AS production + +ARG VERSION=0.0.1-SNAPSHOT +ARG ENABLE_COVERAGE=false + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ + apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + wget && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=builder /build/controller . +COPY --from=policies . /app/default-policies +COPY --from=target LICENSE /app/LICENSE +COPY --from=gateway-controller lua /app/lua +COPY --from=gateway-controller default-llm-provider-templates /app/default-llm-provider-templates + +RUN mkdir -p /app/data && \ + if [ "$ENABLE_COVERAGE" = "true" ]; then mkdir -p /coverage; fi + +RUN groupadd -r -g 10001 wso2 && \ + useradd -r -u 10001 -g wso2 -s /usr/sbin/nologin -c "WSO2 Application User" wso2 && \ + chown -R wso2:wso2 /app/data && \ + chmod -R 755 /app/data && \ + if [ "$ENABLE_COVERAGE" = "true" ]; then \ + chown -R wso2:wso2 /coverage && \ + chmod -R 755 /coverage; \ + fi + +ENV GOCOVERDIR=/coverage + +USER wso2 + +EXPOSE 9090 18000 + +LABEL org.opencontainers.image.title="API Platform Event-Gateway-Controller" +LABEL org.opencontainers.image.description="API Platform Gateway Controller with WebSub/WebBroker (event-gateway) support" +LABEL org.opencontainers.image.vendor="WSO2" +LABEL org.opencontainers.image.version="${VERSION}" + +ENTRYPOINT ["/app/controller"] diff --git a/event-gateway/gateway-controller/Makefile b/event-gateway/gateway-controller/Makefile new file mode 100644 index 0000000000..a55fa2aecd --- /dev/null +++ b/event-gateway/gateway-controller/Makefile @@ -0,0 +1,137 @@ +# -------------------------------------------------------------------- +# 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. +# -------------------------------------------------------------------- + +# Makefile for Event-Gateway-Controller (gateway-controller core + WebSub/WebBroker support) + +VERSION ?= $(shell cat ../VERSION 2>/dev/null || echo "0.0.1-SNAPSHOT") +GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") +BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") + +DOCKER_REGISTRY ?= ghcr.io/wso2/api-platform +IMAGE_NAME := $(DOCKER_REGISTRY)/event-gateway-controller + +# Default policy manifests bundled into the image (control-plane default-policy +# metadata, not compiled plugin code — unrelated to gateway/build.yaml). +POLICIES_BUILD_CONTEXT ?= ../default-policies + +.PHONY: help generate-server-code test build push build-and-push-multiarch build-coverage-image build-debug clean + +help: ## Show this help message + @echo 'Usage: make [target]' + @echo '' + @echo 'Available targets:' + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +generate-server-code: ## Generate WebSub/WebBroker API server code from OpenAPI spec + @echo "Generating event-gateway API server code from OpenAPI spec..." + @go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.5.1 --config=oapi-codegen.yaml api/eventgateway-openapi.yaml + +test: ## Run unit tests + @echo "Running tests..." + @go test -v ./... -cover -coverprofile=unit-test-coverage.txt + +build: generate-server-code test ## Build Docker image using buildx + @echo "Building Docker image ($(IMAGE_NAME):$(VERSION))..." + @mkdir -p target + @cp ../../LICENSE target/ + docker buildx build -f Dockerfile \ + --build-context sdk=../../sdk \ + --build-context sdk-core=../../sdk/core \ + --build-context common=../../common \ + --build-context httpkit=../../httpkit \ + --build-context gateway-controller=../../gateway/gateway-controller \ + --build-context policies=$(POLICIES_BUILD_CONTEXT) \ + --build-context target=target \ + --build-arg VERSION=$(VERSION) \ + --build-arg GIT_COMMIT=$(GIT_COMMIT) \ + --target production \ + -t $(IMAGE_NAME):$(VERSION) \ + -t $(IMAGE_NAME):latest \ + --load \ + . + @rm -rf target + @echo "Docker image ($(IMAGE_NAME):$(VERSION)) built successfully." + +push: ## Push Docker image to registry + docker push $(IMAGE_NAME):$(VERSION) + docker push $(IMAGE_NAME):latest + +build-and-push-multiarch: ## Build and push multi-architecture Docker image (linux/amd64, linux/arm64) + @mkdir -p target + @cp ../../LICENSE target/ + docker buildx build -f Dockerfile \ + --build-context sdk=../../sdk \ + --build-context sdk-core=../../sdk/core \ + --build-context common=../../common \ + --build-context httpkit=../../httpkit \ + --build-context gateway-controller=../../gateway/gateway-controller \ + --build-context policies=$(POLICIES_BUILD_CONTEXT) \ + --build-context target=target \ + --platform linux/amd64,linux/arm64 \ + --build-arg VERSION=$(VERSION) \ + --build-arg GIT_COMMIT=$(GIT_COMMIT) \ + --target production \ + -t $(IMAGE_NAME):$(VERSION) \ + -t $(IMAGE_NAME):latest \ + --push \ + . + @rm -rf target + +build-coverage-image: test ## Build coverage-instrumented event-gateway-controller image + @mkdir -p target + @cp ../../LICENSE target/ + docker buildx build -f Dockerfile \ + --build-context sdk=../../sdk \ + --build-context sdk-core=../../sdk/core \ + --build-context common=../../common \ + --build-context httpkit=../../httpkit \ + --build-context gateway-controller=../../gateway/gateway-controller \ + --build-context policies=$(POLICIES_BUILD_CONTEXT) \ + --build-context target=target \ + --build-arg VERSION=$(VERSION) \ + --build-arg GIT_COMMIT=$(GIT_COMMIT) \ + --build-arg ENABLE_COVERAGE=true \ + --target production \ + -t $(IMAGE_NAME)-coverage:$(VERSION) \ + --load \ + . + @rm -rf target + +build-debug: ## Build debug image for remote debugging with dlv (VS Code attach on port 2345) + @mkdir -p target + @cp ../../LICENSE target/ + docker buildx build -f Dockerfile \ + --build-context sdk=../../sdk \ + --build-context sdk-core=../../sdk/core \ + --build-context common=../../common \ + --build-context httpkit=../../httpkit \ + --build-context gateway-controller=../../gateway/gateway-controller \ + --build-context policies=$(POLICIES_BUILD_CONTEXT) \ + --build-context target=target \ + --build-arg VERSION=$(VERSION) \ + --build-arg GIT_COMMIT=$(GIT_COMMIT) \ + --build-arg ENABLE_DEBUG=true \ + --target debug \ + -t $(IMAGE_NAME)-debug:$(VERSION) \ + -t $(IMAGE_NAME)-debug:latest \ + --load \ + . + @rm -rf target + +clean: ## Remove SQLite database files from data/ + @rm -f data/*.db data/*.db-shm data/*.db-wal diff --git a/event-gateway/gateway-controller/api/eventgateway-openapi.yaml b/event-gateway/gateway-controller/api/eventgateway-openapi.yaml new file mode 100644 index 0000000000..00f31b2abd --- /dev/null +++ b/event-gateway/gateway-controller/api/eventgateway-openapi.yaml @@ -0,0 +1,2019 @@ +openapi: 3.0.3 +info: + title: Event Gateway Controller Management API + description: | + REST API for managing WebSub and WebBroker API configurations in the + WSO2 API Platform event-gateway-controller. + version: 1.0.0 + contact: + name: WSO2 API Platform Team +servers: + - url: https://localhost:9090/api/management/v1 + description: Local development + - url: https://event-gateway-controller:9090/api/management/v1 + description: Docker/Kubernetes deployment +security: + - basicAuth: [] +paths: + /websub-apis: + post: + summary: Create a new WebSubAPI + description: Add a new WebSubAPI to the Gateway. + operationId: createWebSubAPI + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/WebSubAPIRequest" + application/json: + schema: + $ref: "#/components/schemas/WebSubAPIRequest" + responses: + "201": + description: WebSubAPI created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/WebSubAPI" + "400": + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "409": + description: Conflict - WebSub 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 WebSubAPIs + description: List WebSubAPIs registered in the Gateway, optionally filtered by name, version, context, or status. + operationId: listWebSubAPIs + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: displayName + in: query + required: false + description: Filter by WebSub API display name + schema: + type: string + example: Weather WebSub API + - name: version + in: query + required: false + description: Filter by WebSub API version + schema: + type: string + example: v1.0 + - name: context + in: query + required: false + description: Filter by WebSub API context/path + schema: + type: string + example: /weather-events + - name: status + in: query + required: false + description: Filter by deployment status + schema: + type: string + enum: [ deployed, undeployed ] + example: undeployed + responses: + "200": + description: List of WebSubAPIs + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: success + count: + type: integer + example: 5 + apis: + type: array + items: + $ref: "#/components/schemas/WebSubAPI" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /websub-apis/{id}/api-keys: + post: + summary: Create a new API key for a WebSub API + description: 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. + operationId: createWebSubAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API to generate the key for + schema: + type: string + example: weather-websub-api + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/APIKeyCreationRequest" + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationRequest" + responses: + '201': + description: API key created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationResponse" + '400': + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '409': + description: Conflict (duplicate key or conflicting update) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebSub API not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + get: + summary: Get the list of API keys for a WebSub API + description: List all API keys for a WebSub API in the Gateway. + operationId: listWebSubAPIKeys + x-basicauth-roles: [admin, consumer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API to retrieve the keys for + schema: + type: string + example: weather-websub-api + responses: + '200': + description: List of API keys + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyListResponse" + '404': + description: WebSub API not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /websub-apis/{id}/api-keys/{apiKeyName}/regenerate: + post: + summary: Regenerate API key for a WebSub API + description: 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_`. + operationId: regenerateWebSubAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API + schema: + type: string + example: weather-websub-api + - name: apiKeyName + in: path + required: true + description: Name of the API key to regenerate + schema: + type: string + example: weather-api-key + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/APIKeyRegenerationRequest" + application/json: + schema: + $ref: "#/components/schemas/APIKeyRegenerationRequest" + responses: + '200': + description: API key rotated successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationResponse" + '400': + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebSub API or API key not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /websub-apis/{id}/api-keys/{apiKeyName}: + put: + summary: Update an API key for a WebSub API + description: Update an API key with a custom value instead of auto-generating one. + operationId: updateWebSubAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API + schema: + type: string + example: weather-websub-api + - name: apiKeyName + in: path + required: true + description: Name of the API key to update + schema: + type: string + example: weather-api-key + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/APIKeyUpdateRequest" + application/json: + schema: + $ref: "#/components/schemas/APIKeyUpdateRequest" + responses: + '200': + description: API key updated successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationResponse" + '400': + description: Invalid request (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '409': + description: Conflict (duplicate key or conflicting update) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebSub API or API key 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: Revoke an API key for a WebSub API + description: Revoke an API key. Once revoked, it can no longer be used to authenticate requests. + operationId: revokeWebSubAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API + schema: + type: string + example: weather-websub-api + - name: apiKeyName + in: path + required: true + description: Name of the API key to revoke + schema: + type: string + example: weather-api-key + responses: + '200': + description: API key revoked successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyRevocationResponse" + '400': + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebSub API or API key not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /websub-apis/{id}/secrets: + post: + summary: Generate a new HMAC secret for a WebSub API + description: | + Generates a cryptographically secure HMAC shared secret (format: `whsec_` + 64 hex chars) + for the given WebSub API. The plaintext value is returned **once** in the response and + never stored in plaintext. Use the secret as the `secret` parameter value in the + `websub-hmac-auth` policy to validate incoming webhook event signatures. + operationId: createWebSubAPISecret + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API + schema: + type: string + example: weather-websub-api + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/WebhookSecretCreationRequest" + application/yaml: + schema: + $ref: "#/components/schemas/WebhookSecretCreationRequest" + responses: + '201': + description: Secret generated successfully. The `secret` field will not appear in any future list or get response. + content: + application/json: + schema: + $ref: "#/components/schemas/WebhookSecretCreationResponse" + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebSub API not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '409': + description: A secret with the same name already exists for this API + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + get: + summary: List HMAC secrets for a WebSub API + description: Returns metadata for all active HMAC secrets. Plaintext values are never included. + operationId: listWebSubAPISecrets + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API + schema: + type: string + example: weather-websub-api + responses: + '200': + description: List of secrets (no plaintext) + content: + application/json: + schema: + $ref: "#/components/schemas/WebhookSecretListResponse" + '404': + description: WebSub API not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /websub-apis/{id}/secrets/{secretName}/regenerate: + post: + summary: Regenerate (rotate) a WebSub API HMAC secret + description: | + Replaces the secret value with a newly generated one. The old secret stops + validating signatures immediately. The new plaintext is returned once and + never stored unencrypted. + operationId: regenerateWebSubAPISecret + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API + schema: + type: string + example: weather-websub-api + - name: secretName + in: path + required: true + description: Name of the secret to regenerate + schema: + type: string + example: github-webhook + responses: + '200': + description: Secret rotated successfully. The new `secret` value will not appear again. + content: + application/json: + schema: + $ref: "#/components/schemas/WebhookSecretCreationResponse" + '404': + description: WebSub API or secret not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /websub-apis/{id}/secrets/{secretName}: + delete: + summary: Delete a WebSub API HMAC secret + description: Permanently removes the secret. Existing webhook deliveries that used this secret will immediately fail signature validation. + operationId: deleteWebSubAPISecret + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebSub API + schema: + type: string + example: weather-websub-api + - name: secretName + in: path + required: true + description: Name of the secret to delete + schema: + type: string + example: github-webhook + responses: + '204': + description: Secret deleted successfully + '404': + description: WebSub API or secret not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /websub-apis/{id}: + get: + summary: Get WebSubAPI by id + description: Get a WebSubAPI by its ID. + operationId: getWebSubAPIById + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: | + Unique public identifier for the WebSub API. + schema: + type: string + example: weather-websub-api + responses: + "200": + description: WebSubAPI details + content: + application/json: + schema: + $ref: "#/components/schemas/WebSubAPI" + application/yaml: + schema: + $ref: "#/components/schemas/WebSubAPI" + "404": + description: WebSubAPI not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + put: + summary: Update an existing WebSubAPI + description: Update an existing WebSubAPI in the Gateway. + operationId: updateWebSubAPI + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: | + Unique public identifier of the WebSub API to update. + schema: + type: string + example: weather-websub-api + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/WebSubAPIRequest" + application/json: + schema: + $ref: "#/components/schemas/WebSubAPIRequest" + responses: + "200": + description: WebSubAPI updated successfully + content: + application/json: + schema: + $ref: "#/components/schemas/WebSubAPI" + "400": + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: WebSubAPI 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 WebSubAPI + description: Delete a WebSubAPI from the Gateway. + operationId: deleteWebSubAPI + x-basicauth-roles: [admin, developer] + tags: + - WebSub API Management + parameters: + - name: id + in: path + required: true + description: | + Unique public identifier of the WebSub API to delete. + schema: + type: string + example: weather-websub-api + responses: + "200": + description: WebSubAPI deleted successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: success + message: + type: string + example: WebSubAPI deleted successfully + id: + type: string + example: weather-websub-api + "404": + description: WebSubAPI not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "500": + description: Internal server error + content: + application/json: + 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" + + /webbroker-apis/{id}/api-keys: + post: + summary: Create a new API key for a WebBroker API + description: Generate a new API key for a WebBroker 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. + operationId: createWebBrokerAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebBroker API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebBroker API to generate the key for + schema: + type: string + example: stock-trading-webbroker-api + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/APIKeyCreationRequest" + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationRequest" + responses: + '201': + description: API key created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationResponse" + '400': + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '409': + description: Conflict (duplicate key or conflicting update) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebBroker API not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + get: + summary: Get the list of API keys for a WebBroker API + description: List all API keys for a WebBroker API in the Gateway. + operationId: listWebBrokerAPIKeys + x-basicauth-roles: [admin, consumer] + tags: + - WebBroker API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebBroker API to retrieve the keys for + schema: + type: string + example: stock-trading-webbroker-api + responses: + '200': + description: List of API keys + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyListResponse" + '404': + description: WebBroker API not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate: + post: + summary: Regenerate API key for a WebBroker API + description: Regenerate an existing API key for a WebBroker API in the Gateway. The previous key is revoked and replaced with a new 32-byte random value encoded in hexadecimal, prefixed with `apip_`. + operationId: regenerateWebBrokerAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebBroker API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebBroker API + schema: + type: string + example: stock-trading-webbroker-api + - name: apiKeyName + in: path + required: true + description: Name of the API key to regenerate + schema: + type: string + example: trading-api-key + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/APIKeyRegenerationRequest" + application/json: + schema: + $ref: "#/components/schemas/APIKeyRegenerationRequest" + responses: + '200': + description: API key rotated successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationResponse" + '400': + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebBroker API or API key not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /webbroker-apis/{id}/api-keys/{apiKeyName}: + put: + summary: Update an API key for a WebBroker API + description: Update an API key with a custom value instead of auto-generating one. + operationId: updateWebBrokerAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebBroker API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebBroker API + schema: + type: string + example: stock-trading-webbroker-api + - name: apiKeyName + in: path + required: true + description: Name of the API key to update + schema: + type: string + example: trading-api-key + requestBody: + required: true + content: + application/yaml: + schema: + $ref: "#/components/schemas/APIKeyUpdateRequest" + application/json: + schema: + $ref: "#/components/schemas/APIKeyUpdateRequest" + responses: + '200': + description: API key updated successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyCreationResponse" + '400': + description: Invalid request (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '409': + description: Conflict (duplicate key or conflicting update) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebBroker API or API key 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: Revoke an API key for a WebBroker API + description: Revoke an API key. Once revoked, it can no longer be used to authenticate requests. + operationId: revokeWebBrokerAPIKey + x-basicauth-roles: [admin, consumer] + tags: + - WebBroker API Management + parameters: + - name: id + in: path + required: true + description: Unique public identifier of the WebBroker API + schema: + type: string + example: stock-trading-webbroker-api + - name: apiKeyName + in: path + required: true + description: Name of the API key to revoke + schema: + type: string + example: trading-api-key + responses: + '200': + description: API key revoked successfully + content: + application/json: + schema: + $ref: "#/components/schemas/APIKeyRevocationResponse" + '400': + description: Invalid configuration (validation failed) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: WebBroker API not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + schemas: + APIKey: + type: object + description: Details of an API key + properties: + name: + type: string + description: URL-safe identifier for the API key (auto-generated from displayName, immutable, used as path parameter) + pattern: "^[a-z0-9]+(-[a-z0-9]+)*$" + minLength: 3 + maxLength: 63 + example: my-production-key + displayName: + type: string + description: Human-readable name for the API key (user-provided, mutable) + minLength: 1 + maxLength: 100 + example: My Production Key + apiKey: + type: string + description: Generated API key with apip_ prefix + example: "apip_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + apiId: + type: string + description: Unique public identifier of the API that the key is associated with + example: reading-list-api-v1.0 + status: + type: string + description: Status of the API key + enum: + - active + - revoked + - expired + example: active + createdAt: + type: string + format: date-time + description: Timestamp when the API key was generated + example: "2026-04-01T10:30:00Z" + createdBy: + type: string + description: Identifier of the user who generated the API key + example: admin + expiresAt: + type: string + format: date-time + nullable: true + description: Expiration timestamp (null if no expiration) + example: "2027-01-01T00:00:00Z" + source: + type: string + description: Source of the API key (local or external) + enum: + - local + - external + example: local + externalRefId: + type: string + description: External reference ID for the API key + example: "cloud-apim-key-98765" + required: + - name + - apiId + - status + - createdAt + - createdBy + - expiresAt + - source + example: + 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 + APIKeyCreationRequest: + type: object + properties: + name: + type: string + description: Identifier of the API key. If not provided, a default identifier will be generated + pattern: "^[a-z0-9]+(-[a-z0-9]+)*$" + minLength: 3 + maxLength: 63 + example: my-production-key + apiKey: + type: string + minLength: 36 + description: | + Optional plain-text API key value for external key injection. + If provided, this key will be used instead of generating a new one. + The key will be hashed before storage. The key can be in any format + (minimum 36 characters). Use this for injecting externally generated + API keys. + example: "xxxxxx-wso2-api-platform-key-xxxxxx-xxxxxxx" + maskedApiKey: + type: string + description: | + Masked version of the API key for display purposes. + Provided by the platform API when injecting pre-hashed keys. + example: "apip_****xyz789" + expiresIn: + type: object + description: Expiration duration for the API key + properties: + unit: + type: string + description: Time unit for expiration + enum: + - seconds + - minutes + - hours + - days + - weeks + - months + example: days + duration: + type: integer + description: Duration value for expiration + example: 30 + required: + - unit + - duration + expiresAt: + type: string + format: date-time + description: Expiration timestamp. If both expiresIn and expiresAt are provided, expiresAt takes precedence. + example: "2026-12-08T10:30:00Z" + externalRefId: + type: string + description: | + External reference ID for the API key. + This field is optional and used for tracing purposes only. + The gateway generates its own internal ID for tracking. + example: "cloud-apim-key-98765" + issuer: + type: string + description: | + Identifies the portal that created this key. If provided, only api keys generated from + the same portal will be accepted. If not provided, there is no portal restriction. + example: "api-platform-devportal" + example: + name: my-production-key + APIKeyCreationResponse: + type: object + properties: + status: + type: string + example: success + message: + type: string + example: API key generated successfully + remainingApiKeyQuota: + type: integer + description: Remaining API key quota for the user + example: 9 + apiKey: + $ref: '#/components/schemas/APIKey' + required: + - status + - message + example: + 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 + APIKeyListResponse: + type: object + properties: + apiKeys: + type: array + items: + $ref: "#/components/schemas/APIKey" + totalCount: + type: integer + description: Total number of API keys + example: 3 + status: + type: string + example: success + APIKeyRegenerationRequest: + type: object + properties: + expiresIn: + type: object + description: Expiration duration for the API key + properties: + unit: + type: string + description: Time unit for expiration + enum: + - seconds + - minutes + - hours + - days + - weeks + - months + example: days + duration: + type: integer + description: Duration value for expiration + example: 30 + required: + - unit + - duration + expiresAt: + type: string + format: date-time + description: Expiration timestamp + example: "2026-12-08T10:30:00Z" + example: {} + APIKeyRevocationResponse: + type: object + properties: + status: + type: string + example: success + message: + type: string + example: API key revoked successfully + required: + - status + - message + APIKeyUpdateRequest: + allOf: + - $ref: "#/components/schemas/APIKeyCreationRequest" + required: + - apiKey + ErrorResponse: + type: object + required: + - status + - message + properties: + status: + type: string + example: error + message: + type: string + description: High-level error description + example: Configuration validation failed + errors: + type: array + description: Detailed validation errors + items: + $ref: "#/components/schemas/ValidationError" + Metadata: + type: object + required: + - name + properties: + name: + type: string + description: Unique handle for the resource + example: reading-list-api-v1.0 + labels: + type: object + description: Labels are key-value pairs for organizing and selecting APIs. Keys must not contain spaces. + additionalProperties: + type: string + example: + environment: production + team: backend + version: v1 + annotations: + type: object + description: Annotations are arbitrary non-identifying metadata. Use domain-prefixed keys. + additionalProperties: + type: string + example: + gateway.api-platform.wso2.com/project-id: 019d953f-d386-7a64-aa92-1869a28292e0 + Policy: + type: object + required: + - name + - version + properties: + name: + type: string + description: Name of the policy + example: cors + version: + type: string + description: > + 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. + pattern: '^v\d+$' + example: v1 + executionCondition: + type: string + description: Expression controlling conditional execution of the policy + example: "request.metadata[authenticated] != true" + params: + type: object + description: Arbitrary parameters for the policy (free-form key/value structure) + additionalProperties: true + + # ----------------------- + # Webhook (Async) API schema + # ----------------------- + ResourceStatus: + type: object + description: Server-managed lifecycle information for a resource + properties: + id: + type: string + description: Unique identifier assigned by the server (equal to metadata.name) + example: reading-list-api-v1.0 + state: + type: string + description: Desired deployment state reported by the server + enum: + - deployed + - undeployed + example: deployed + createdAt: + type: string + format: date-time + description: Timestamp when the resource was first created (UTC) + example: 2026-04-24T07:21:13Z + updatedAt: + type: string + format: date-time + description: Timestamp when the resource was last updated (UTC) + example: 2026-04-24T07:21:13Z + deployedAt: + type: string + format: date-time + description: Timestamp when the resource was last deployed (omitted when undeployed) + example: 2026-04-24T07:21:13Z + + # Request body for create/update: user/resource fields only (no server-managed status). + ValidationError: + type: object + properties: + field: + type: string + description: Field that failed validation + example: spec.context + message: + type: string + description: Human-readable error message + example: Context must start with / and cannot end with / + + 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/v1 + 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 + broker: + name: kafka-driver + type: kafka + properties: + brokers: + - 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 + on_connection_init: + policies: [] + on_produce: + policies: [] + on_consume: + policies: [] + 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 + WebBrokerApiAllChannelPolicies: + type: object + description: Protocol mediation policies applied to all channels + properties: + on_connection_init: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + on_produce: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + on_consume: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + WebBrokerApiBroker: + 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: + brokers: + - kafka-broker-1:9092 + - kafka-broker-2:9092 + WebBrokerApiChannel: + type: object + description: WebSocket channel configuration with Kafka topic mapping + properties: + produceTo: + $ref: '#/components/schemas/WebBrokerApiProduceConfig' + consumeFrom: + $ref: '#/components/schemas/WebBrokerApiConsumeConfig' + on_connection_init: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + on_produce: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + on_consume: + $ref: '#/components/schemas/WebBrokerApiPolicyGroup' + 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 + WebBrokerApiData: + type: object + required: + - displayName + - version + - context + - receiver + - broker + - 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' + 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) + 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 + WebBrokerApiPolicyGroup: + type: object + description: Group of policies + properties: + policies: + type: array + description: List of policies to apply + items: + $ref: '#/components/schemas/Policy' + 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 + 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 + WebBrokerApiRequest: + type: object + required: + - apiVersion + - metadata + - kind + - spec + properties: + apiVersion: + type: string + description: API specification version + example: gateway.api-platform.wso2.com/v1 + enum: + - gateway.api-platform.wso2.com/v1 + 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/v1 + 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 + broker: + name: kafka-driver + type: kafka + properties: + brokers: + - 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 + on_connection_init: + policies: [] + on_produce: + policies: [] + on_consume: + policies: [] + WebSubAPI: + allOf: + - $ref: '#/components/schemas/WebSubAPIRequest' + - 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/v1 + kind: WebSubApi + metadata: + name: github-events-v1.0 + spec: + displayName: GitHub Events + version: v1.0 + context: /github-events/$version + channels: + - name: issues + method: SUB + - name: pull_requests + method: SUB + status: + id: github-events-v1.0 + state: deployed + createdAt: 2026-04-24T07:21:13Z + updatedAt: 2026-04-24T07:21:13Z + deployedAt: 2026-04-24T07:21:13Z + WebSubAPIRequest: + type: object + required: + - apiVersion + - metadata + - kind + - spec + properties: + apiVersion: + type: string + description: API specification version + example: gateway.api-platform.wso2.com/v1 + enum: + - gateway.api-platform.wso2.com/v1 + kind: + type: string + description: API type + example: WebSubApi + enum: + - WebSubApi + metadata: + $ref: "#/components/schemas/Metadata" + spec: + $ref: '#/components/schemas/WebhookAPIData' + example: + apiVersion: gateway.api-platform.wso2.com/v1 + kind: WebSubApi + metadata: + name: github-events-v1.0 + spec: + displayName: GitHub Events + version: v1.0 + context: /github-events/$version + channels: + - name: issues + method: SUB + - name: pull_requests + method: SUB + WebSubAllChannelPolicies: + type: object + description: Policies applied to all channels, organized by event type. + properties: + on_subscription: + $ref: '#/components/schemas/WebSubEventPolicies' + on_unsubscription: + $ref: '#/components/schemas/WebSubEventPolicies' + on_message_received: + $ref: '#/components/schemas/WebSubEventPolicies' + on_message_delivery: + $ref: '#/components/schemas/WebSubEventPolicies' + + # WebSubChannelPolicies defines per-channel policies organized by event type. + WebSubChannel: + type: object + description: A single channel definition with optional per-channel policy overrides. + properties: + on_subscription: + $ref: '#/components/schemas/WebSubEventPolicies' + on_unsubscription: + $ref: '#/components/schemas/WebSubEventPolicies' + on_message_received: + $ref: '#/components/schemas/WebSubEventPolicies' + on_message_delivery: + $ref: '#/components/schemas/WebSubEventPolicies' + + # WebSubEventPolicies defines policies for a single event type. + WebSubEventPolicies: + type: object + description: Policies for a single event type. + properties: + policies: + type: array + description: List of policies applied for this event type. + items: + $ref: '#/components/schemas/Policy' + + # WebSubAllChannelPolicies defines policies applied to all channels for each event type. + WebhookAPIData: + type: object + required: + - displayName + - version + - context + 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: reading-list-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: /weather + 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 + allChannels: + $ref: '#/components/schemas/WebSubAllChannelPolicies' + channels: + type: object + description: Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel. + additionalProperties: + $ref: '#/components/schemas/WebSubChannel' + deploymentState: + type: string + description: 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. + enum: [deployed, undeployed] + default: deployed + example: deployed + + # WebSubChannel defines a single channel entry with its per-channel policies. + WebhookSecretCreationRequest: + type: object + required: + - displayName + properties: + displayName: + type: string + description: Human-readable label for this secret (used to derive the immutable name slug). + minLength: 1 + maxLength: 100 + example: GitHub Webhook + WebhookSecretCreationResponse: + type: object + properties: + status: + type: string + example: success + message: + type: string + example: Webhook secret generated successfully + secret: + type: string + description: | + The generated plaintext secret value (whsec_ prefix + 64 hex chars). + Returned exactly once — store it immediately as it will not be retrievable again. + example: "whsec_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + webhookSecret: + $ref: '#/components/schemas/WebhookSecretInfo' + required: + - status + - message + - secret + WebhookSecretInfo: + type: object + description: Metadata for an HMAC secret. The plaintext value is never included. + properties: + name: + type: string + description: URL-safe slug (immutable, used as path parameter for regenerate/delete). + example: github-webhook + displayName: + type: string + description: Human-readable label. + example: GitHub Webhook + status: + type: string + enum: [active, revoked] + example: active + createdAt: + type: string + format: date-time + example: "2026-06-01T10:00:00Z" + updatedAt: + type: string + format: date-time + example: "2026-06-01T10:00:00Z" + WebhookSecretListResponse: + type: object + properties: + status: + type: string + example: success + totalCount: + type: integer + description: Total number of active secrets for this API + example: 2 + secrets: + type: array + items: + $ref: '#/components/schemas/WebhookSecretInfo' diff --git a/event-gateway/gateway-controller/cmd/controller/main.go b/event-gateway/gateway-controller/cmd/controller/main.go new file mode 100644 index 0000000000..c93fec239c --- /dev/null +++ b/event-gateway/gateway-controller/cmd/controller/main.go @@ -0,0 +1,789 @@ +/* + * 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. + */ + +// Command controller is the event-gateway-controller binary: gateway-controller +// (core) plus WebSub/WebBroker management-API support. It imports +// gateway-controller as a library (the abstraction layer) and supplies the +// concrete implementations for the extension points core exposes: +// xds.EventGatewayXDSHooks, policyxds.EventChannelTranslator, +// restapi.SetWebSubTopicDeregistrar, storage.SetWebSubTopicUpdater, +// eventlistener.WebhookSecretEventHandler, and policyxds.WebhookSecretCacheProvider +// / controlplane.WebhookSecretSnapshotRefresher. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "log/slog" + "net/http" + "os" + "os/signal" + "runtime" + "strings" + "syscall" + "time" + + "github.com/wso2/api-platform/common/authenticators" + "github.com/wso2/api-platform/common/eventhub" + commonmodels "github.com/wso2/api-platform/common/models" + "github.com/wso2/api-platform/common/webhooksecret" + + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/adminserver" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/handlers" + 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/apikeyxds" + coreconfig "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/controlplane" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/encryption" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/encryption/aesgcm" + coreeventlistener "github.com/wso2/api-platform/gateway/gateway-controller/pkg/eventlistener" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/immutable" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/lazyresourcexds" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/logger" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/metrics" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/policyxds" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/secrets" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/service/restapi" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/subscriptionxds" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/transform" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" + coreversion "github.com/wso2/api-platform/gateway/gateway-controller/pkg/version" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/xds" + gohttpkit "github.com/wso2/go-httpkit/middleware" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" + eventgatewayconfig "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/eventlistener" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/handler" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/hubtopic" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/kindsupport" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/policyhooks" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/translator" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/webhooksecretservice" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/webhooksecretxds" +) + +const ( + managementAPIBasePath = "/api/management/v1" +) + +func toBackendConfig(cfg *coreconfig.Config) storage.BackendConfig { + pg := cfg.Controller.Storage.EffectivePostgresConfig() + ms := cfg.Controller.Storage.EffectiveSQLServerConfig() + return storage.BackendConfig{ + Type: cfg.Controller.Storage.Type, + SQLitePath: cfg.Controller.Storage.EffectiveSQLitePath(), + Postgres: storage.PostgresConnectionConfig{ + DSN: pg.DSN, + Host: pg.Host, + Port: pg.Port, + Database: pg.Database, + User: pg.User, + Password: pg.Password, + SSLMode: pg.SSLMode, + ConnectTimeout: pg.ConnectTimeout, + MaxOpenConns: pg.MaxOpenConns, + MaxIdleConns: pg.MaxIdleConns, + ConnMaxLifetime: pg.ConnMaxLifetime, + ConnMaxIdleTime: pg.ConnMaxIdleTime, + ApplicationName: pg.ApplicationName, + }, + SQLServer: storage.SQLServerConnectionConfig{ + DSN: ms.DSN, + Host: ms.Host, + Port: ms.Port, + Database: ms.Database, + User: ms.User, + Password: ms.Password, + Encrypt: cfg.Controller.Storage.SQLServerEncrypt(), + TrustServerCertificate: cfg.Controller.Storage.SQLServerTrustServerCertificate(), + ConnectTimeout: ms.ConnectTimeout, + MaxOpenConns: ms.MaxOpenConns, + MaxIdleConns: ms.MaxIdleConns, + ConnMaxLifetime: ms.ConnMaxLifetime, + ConnMaxIdleTime: ms.ConnMaxIdleTime, + ApplicationName: ms.ApplicationName, + }, + GatewayID: cfg.Controller.Server.GatewayID, + } +} + +func main() { + configPath := flag.String("config", "", "Path to configuration file (required)") + flag.Parse() + + if *configPath == "" { + fmt.Fprintf(os.Stderr, "Error: -config flag is required\n") + fmt.Fprintf(os.Stderr, "Usage: %s -config \n", os.Args[0]) + os.Exit(1) + } + + // Register WebSubApi/WebBrokerApi kind support into core's registries + // before any config is loaded/deployed/validated. + kindsupport.Register() + + cfg, err := coreconfig.LoadConfig(*configPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load configuration from %s: %v\n", *configPath, err) + os.Exit(1) + } + + // This module's own EventGatewayConfig section, loaded from the same file. + eventGatewayCfg, err := eventgatewayconfig.Load(*configPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load event_gateway configuration from %s: %v\n", *configPath, err) + os.Exit(1) + } + if eventGatewayCfg.Enabled { + if err := eventGatewayCfg.Validate(); err != nil { + fmt.Fprintf(os.Stderr, "Invalid event_gateway configuration: %v\n", err) + os.Exit(1) + } + } + + metrics.SetEnabled(cfg.Controller.Metrics.Enabled) + metrics.Init() + + log := logger.NewLogger(logger.Config{ + Level: cfg.Controller.Logging.Level, + Format: cfg.Controller.Logging.Format, + }) + + log.Info("Starting Event-Gateway-Controller", + slog.String("version", coreversion.Version), + slog.String("git_commit", coreversion.GitCommit), + slog.String("build_date", coreversion.BuildDate), + slog.String("config_file", *configPath), + slog.String("storage_type", cfg.Controller.Storage.Type), + slog.Bool("event_gateway_enabled", eventGatewayCfg.Enabled), + ) + + if !cfg.Controller.Auth.Basic.Enabled && !cfg.Controller.Auth.IDP.Enabled { + log.Warn("No authentication configured: both basic auth and IDP are disabled. Event-Gateway-Controller API will allow all requests without authentication") + } + + if cfg.ImmutableGateway.Enabled { + if err := immutable.ResetSQLiteFiles(cfg.Controller.Storage.EffectiveSQLitePath(), log); err != nil { + log.Error("Failed to reset SQLite files for immutable mode", slog.Any("error", err)) + os.Exit(1) + } + } + + var db storage.Storage + db, err = storage.NewStorage(toBackendConfig(cfg), log) + if err != nil { + if strings.EqualFold(cfg.Controller.Storage.Type, "sqlite") && errors.Is(err, storage.ErrDatabaseLocked) { + log.Error("Database is locked by another process", slog.Any("error", err)) + os.Exit(1) + } + log.Error("Failed to initialize database storage", slog.Any("error", err)) + os.Exit(1) + } + defer db.Close() + + var eventHubInstance eventhub.EventHub + var eventHubStorage storage.Storage + ehBackendCfg := toBackendConfig(cfg) + ehBackendCfg.Postgres.MaxOpenConns = cfg.Controller.EventHub.Database.MaxOpenConns + ehBackendCfg.Postgres.MaxIdleConns = cfg.Controller.EventHub.Database.MaxIdleConns + ehBackendCfg.Postgres.ConnMaxLifetime = cfg.Controller.EventHub.Database.ConnMaxLifetime + ehBackendCfg.Postgres.ConnMaxIdleTime = cfg.Controller.EventHub.Database.ConnMaxIdleTime + ehBackendCfg.SQLServer.MaxOpenConns = cfg.Controller.EventHub.Database.MaxOpenConns + ehBackendCfg.SQLServer.MaxIdleConns = cfg.Controller.EventHub.Database.MaxIdleConns + ehBackendCfg.SQLServer.ConnMaxLifetime = cfg.Controller.EventHub.Database.ConnMaxLifetime + ehBackendCfg.SQLServer.ConnMaxIdleTime = cfg.Controller.EventHub.Database.ConnMaxIdleTime + eventHubStorage, err = storage.NewStorage(ehBackendCfg, log) + if err != nil { + log.Error("Failed to initialize EventHub storage", slog.Any("error", err)) + os.Exit(1) + } + eventHubDB := eventHubStorage.GetDB() + + gatewayID := strings.TrimSpace(cfg.Controller.Server.GatewayID) + if eventHubDB == nil { + log.Error("EventHub storage returned nil database handle") + os.Exit(1) + } + if gatewayID == "" { + log.Error("EventHub requires non-empty gateway ID") + os.Exit(1) + } + eventHubInstance = eventhub.New(eventHubDB, log, eventhub.Config{ + PollInterval: cfg.Controller.EventHub.PollInterval, + CleanupInterval: cfg.Controller.EventHub.CleanupInterval, + RetentionPeriod: cfg.Controller.EventHub.RetentionPeriod, + }) + if err := eventHubInstance.Initialize(); err != nil { + log.Error("Failed to initialize EventHub", slog.Any("error", err)) + os.Exit(1) + } + if err := eventHubInstance.RegisterGateway(gatewayID); err != nil { + log.Error("Failed to register gateway with EventHub", slog.Any("error", err)) + os.Exit(1) + } + + configStore := storage.NewConfigStore() + + // Sync WebSub topics into the shared TopicManager whenever a WebSubApi + // config is stored (core's storage.ConfigStore.Add/Update calls this hook). + configStore.SetWebSubTopicUpdater(kindsupport.UpdateTopicManager) + + apiKeyStore := storage.NewAPIKeyStore(log) + apiKeySnapshotManager := apikeyxds.NewAPIKeySnapshotManager(apiKeyStore, log) + apiKeyXDSManager := apikeyxds.NewAPIKeyStateManager(apiKeyStore, apiKeySnapshotManager, log) + + lazyResourceStore := storage.NewLazyResourceStore(log) + lazyResourceSnapshotManager := lazyresourcexds.NewLazyResourceSnapshotManager(lazyResourceStore, log) + lazyResourceXDSManager := lazyresourcexds.NewLazyResourceStateManager(lazyResourceStore, lazyResourceSnapshotManager, log) + + var encryptionProviderManager *encryption.ProviderManager + var secretsService *secrets.SecretService + + if err := storage.LoadFromDatabase(db, configStore); err != nil { + log.Error("Failed to load configurations from database", slog.Any("error", err)) + os.Exit(1) + } + if err := storage.LoadLLMProviderTemplatesFromDatabase(db, configStore); err != nil { + log.Error("Failed to load llm provider template configurations from database", slog.Any("error", err)) + os.Exit(1) + } + + if err := storage.LoadAPIKeysFromDatabase(db, configStore, apiKeyStore); err != nil { + log.Error("Failed to load API keys from database", slog.Any("error", err)) + os.Exit(1) + } + + var webhookSecretStore *webhooksecret.WebhookSecretStore + var webhookSecretSnapshotManager *webhooksecretxds.SnapshotManager + var webhookSecretService *webhooksecretservice.WebhookSecretService + + if len(cfg.Controller.Encryption.Providers) > 0 { + var providers []encryption.EncryptionProvider + for _, providerConfig := range cfg.Controller.Encryption.Providers { + switch providerConfig.Type { + case "aesgcm": + var keyConfigs []aesgcm.KeyConfig + for _, keyConf := range providerConfig.Keys { + keyConfigs = append(keyConfigs, aesgcm.KeyConfig{ + Version: keyConf.Version, + FilePath: keyConf.FilePath, + }) + } + provider, err := aesgcm.NewAESGCMProvider(keyConfigs, log) + if err != nil { + log.Error("Failed to initialize AES-GCM provider", slog.Any("error", err)) + os.Exit(1) + } + providers = append(providers, provider) + default: + log.Error("Unsupported encryption provider type", slog.String("type", providerConfig.Type)) + os.Exit(1) + } + } + + encryptionProviderManager, err = encryption.NewProviderManager(providers, log) + if err != nil { + log.Error("Failed to initialize provider manager", slog.Any("error", err)) + os.Exit(1) + } + secretsService = secrets.NewSecretsService(db, encryptionProviderManager, log) + + // Webhook-secret (HMAC) infra — this binary owns it entirely; core only + // knows about it through the WebhookSecretEventHandler / + // WebhookSecretCacheProvider / WebhookSecretSnapshotRefresher interfaces. + webhookSecretStore = webhooksecret.GetStoreInstance() + webhookSecretSnapshotManager = webhooksecretxds.NewSnapshotManager(webhookSecretStore, log) + webhookSecretService = webhooksecretservice.NewWebhookSecretService(db, encryptionProviderManager, webhookSecretStore, eventHubInstance, gatewayID, log) + + log.Info("Loading webhook secrets from database") + if err := storage.LoadWebhookSecretsFromDatabase(db, encryptionProviderManager, webhookSecretStore); err != nil { + log.Error("Failed to load webhook secrets from database", slog.Any("error", err)) + os.Exit(1) + } + if err := webhookSecretSnapshotManager.RefreshSnapshot(); err != nil { + log.Warn("Failed to generate initial webhook secret xDS snapshot", slog.Any("error", err)) + } + } + + policyLoader := utils.NewPolicyLoader(log) + policyDir := cfg.Controller.Policies.DefinitionsPath + policyDefinitions, err := policyLoader.LoadPoliciesFromDirectory(policyDir) + if err != nil { + log.Error("Failed to load policy definitions", slog.Any("error", err)) + os.Exit(1) + } + + localPolicies, err := policyLoader.GetCustomPolicyNames(cfg.Controller.Policies.BuildManifestPath) + if err != nil { + log.Warn("Could not read build-manifest.yaml, Custom policies will not be marked in the gateway manifest", slog.Any("error", err)) + } + for key, def := range policyDefinitions { + def.ManagedBy = "wso2" + if localPolicies[def.Name+"|"+def.Version] { + def.ManagedBy = "organization" + } + policyDefinitions[key] = def + } + + if err := hydrateStoredConfigsFromDatabaseOnStartup( + configStore, db, &cfg.Router, policyDefinitions, log, + cfg.Controller.Server.SkipInvalidDeploymentsOnStartup, + ); err != nil { + log.Error("Failed to hydrate stored configurations required for startup", slog.Any("error", err)) + os.Exit(1) + } + + snapshotManager := xds.NewSnapshotManager(configStore, log, &cfg.Router, db, cfg) + + // Wire the WebSub xDS translation hooks into the Envoy translator. + snapshotManager.GetTranslator().SetEventGatewayXDSHooks(translator.New(eventGatewayCfg)) + + var sdsSecretManager *xds.SDSSecretManager + xdsTranslator := snapshotManager.GetTranslator() + if xdsTranslator != nil && xdsTranslator.GetCertStore() != nil { + sdsSecretManager = xds.NewSDSSecretManager(xdsTranslator.GetCertStore(), snapshotManager.GetCache(), "router-node", log) + if err := sdsSecretManager.UpdateSecrets(); err != nil { + log.Warn("Failed to initialize SDS secrets", slog.Any("error", err)) + } else { + snapshotManager.SetSDSSecretManager(sdsSecretManager) + } + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + if err := snapshotManager.UpdateSnapshot(ctx, ""); err != nil { + log.Warn("Failed to generate initial xDS snapshot", slog.Any("error", err)) + } + cancel() + + routerConnected := make(chan struct{}) + policyEngineConnected := make(chan struct{}) + + xdsServer := xds.NewServer(snapshotManager, sdsSecretManager, cfg.Controller.Server.XDSPort, log, routerConnected) + go func() { + if err := xdsServer.Start(); err != nil { + log.Error("xDS server failed", slog.Any("error", err)) + os.Exit(1) + } + }() + + if apiKeyXDSManager.GetAPIKeyCount() > 0 { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + if err := apiKeySnapshotManager.UpdateSnapshot(ctx); err != nil { + log.Warn("Failed to generate initial API key snapshot", slog.Any("error", err)) + } + cancel() + } + + policySnapshotManager := policyxds.NewSnapshotManager(log) + runtimeStore := storage.NewRuntimeConfigStore() + policySnapshotManager.SetRuntimeStore(runtimeStore) + policySnapshotManager.SetConfigStore(configStore) + + // Wire the WebSub/WebBroker event-channel translation hooks. + policySnapshotManager.GetTranslator().SetEventChannelHooks(policyhooks.New(log)) + + subscriptionSnapshotManager := subscriptionxds.NewSnapshotManager(db, log) + + policyManager := policyxds.NewPolicyManager(policySnapshotManager, log) + policyManager.SetRuntimeStore(runtimeStore) + + policyVersionResolver := utils.NewLoadedPolicyVersionResolver(policyDefinitions) + restTransformer := transform.NewRestAPITransformer(&cfg.Router, cfg, policyDefinitions) + llmTransformer := transform.NewLLMTransformer(configStore, db, &cfg.Router, cfg, policyDefinitions, policyVersionResolver) + transformerRegistry := transform.NewRegistry(restTransformer, llmTransformer) + policyManager.SetTransformers(transformerRegistry) + + xdsTranslator.SetTransformers(map[string]models.ConfigTransformer{ + "RestApi": transformerRegistry, + "Mcp": transformerRegistry, + "LlmProvider": transformerRegistry, + "LlmProxy": transformerRegistry, + }) + + loadedAPIs := configStore.GetAll() + if _, err := loadRuntimeConfigsFromExistingAPIConfigurations(loadedAPIs, runtimeStore, secretsService, transformerRegistry, log, cfg.Controller.Server.SkipInvalidDeploymentsOnStartup); err != nil { + log.Error("Failed to load runtime configs from API configurations", slog.Any("error", err)) + os.Exit(1) + } + + ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) + if err := policySnapshotManager.UpdateSnapshot(ctx); err != nil { + log.Warn("Failed to generate initial policy xDS snapshot", slog.Any("error", err)) + } + cancel() + + ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) + if err := subscriptionSnapshotManager.UpdateSnapshot(ctx); err != nil { + log.Warn("Failed to generate initial subscription xDS snapshot", slog.Any("error", err)) + } + cancel() + + serverOpts := []policyxds.ServerOption{policyxds.WithOnFirstConnect(policyEngineConnected)} + if cfg.Controller.PolicyServer.TLS.Enabled { + serverOpts = append(serverOpts, policyxds.WithTLS(cfg.Controller.PolicyServer.TLS.CertFile, cfg.Controller.PolicyServer.TLS.KeyFile)) + } + policyXDSServer := policyxds.NewServer(policySnapshotManager, apiKeySnapshotManager, lazyResourceSnapshotManager, subscriptionSnapshotManager, webhookSecretSnapshotManager, cfg.Controller.PolicyServer.Port, log, serverOpts...) + go func() { + if err := policyXDSServer.Start(); err != nil { + log.Error("Policy xDS server failed", slog.Any("error", err)) + os.Exit(1) + } + }() + + templateLoader := utils.NewLLMTemplateLoader(log) + templateDefinitions, err := templateLoader.LoadTemplatesFromDirectory(cfg.Controller.LLM.TemplateDefinitionsPath) + if err != nil { + log.Error("Failed to load llm provider templates", slog.Any("error", err)) + os.Exit(1) + } + + validator := coreconfig.NewAPIValidator() + policyValidator := coreconfig.NewPolicyValidator(policyDefinitions) + validator.SetPolicyValidator(policyValidator) + + apiSvc := utils.NewAPIDeploymentService(configStore, db, snapshotManager, validator, &cfg.Router, eventHubInstance, gatewayID, secretsService) + mcpSvc := utils.NewMCPDeploymentService(configStore, db, snapshotManager, policyManager, policyValidator, eventHubInstance, gatewayID, secretsService) + llmSvc := utils.NewLLMDeploymentService(configStore, db, snapshotManager, lazyResourceXDSManager, templateDefinitions, apiSvc, &cfg.Router, policyVersionResolver, policyValidator) + + cpClient := controlplane.NewClient( + cfg.Controller.ControlPlane, log, configStore, db, snapshotManager, validator, &cfg.Router, + apiKeyXDSManager, apiKeyStore, &cfg.APIKey, policyManager, cfg, policyDefinitions, + lazyResourceXDSManager, templateDefinitions, subscriptionSnapshotManager, eventHubInstance, + secretsService, webhookSecretStore, webhookSecretSnapshotManager, + ) + if err := cpClient.Start(); err != nil { + log.Error("Failed to start control plane client", slog.Any("error", err)) + } + + llmSvc.SetControlPlanePusher(cpClient, cfg.Controller.ControlPlane.DeploymentSyncEnabled) + mcpSvc.SetControlPlanePusher(cpClient, cfg.Controller.ControlPlane.DeploymentSyncEnabled) + + httpClient := &http.Client{Timeout: 10 * time.Second} + + restAPIService := restapi.NewRestAPIService( + configStore, db, snapshotManager, policyManager, apiSvc, apiKeyXDSManager, + cpClient, &cfg.Router, cfg, httpClient, coreconfig.NewParser(), validator, log, + eventHubInstance, secretsService, + ) + // Deregister WebSub hub topics on delete. + restAPIService.SetWebSubTopicDeregistrar(hubtopic.New(apiSvc, httpClient, eventGatewayCfg).Deregister) + + igw := immutable.NewImmutableGW(cfg.ImmutableGateway, restAPIService, llmSvc, mcpSvc) + + authConfig := generateAuthConfig(cfg) + authMiddleWare, err := authenticators.AuthMiddleware(authConfig, log) + if err != nil { + log.Error("Failed to create auth middleware", slog.Any("error", err)) + os.Exit(1) + } + perRouteMiddlewares := []api.MiddlewareFunc{ + authenticators.AuthorizationMiddleware(authConfig, log), + authMiddleWare, + } + eventGatewayPerRouteMiddlewares := []eventgateway.MiddlewareFunc{ + authenticators.AuthorizationMiddleware(authConfig, log), + authMiddleWare, + } + + // Event listener — multi-replica sync, with this binary's own webhook-secret handler wired in. + evtListener := coreeventlistener.NewEventListener( + eventHubInstance, configStore, db, snapshotManager, subscriptionSnapshotManager, + apiKeyXDSManager, lazyResourceXDSManager, policyManager, &cfg.Router, log, cfg, + policyDefinitions, secretsService, + ) + if webhookSecretService != nil { + evtListener.SetWebhookSecretHandler(eventlistener.NewWebhookSecretHandler(db, encryptionProviderManager, webhookSecretStore, webhookSecretSnapshotManager, log)) + } + if err := evtListener.Start(); err != nil { + log.Error("Failed to start event listener", slog.Any("error", err)) + os.Exit(1) + } + + apiServer := handlers.NewAPIServer( + configStore, db, snapshotManager, policyManager, lazyResourceXDSManager, log, cpClient, + policyDefinitions, templateDefinitions, validator, apiKeyXDSManager, cfg, eventHubInstance, + subscriptionSnapshotManager, secretsService, restAPIService, + ) + + eventGatewayHandler := handler.NewWebSubServer(handler.Deps{ + Store: configStore, + DB: db, + DeploymentService: apiSvc, + APIKeyService: utils.NewAPIKeyService(configStore, db, apiKeyXDSManager, &cfg.APIKey, eventHubInstance, gatewayID), + ControlPlaneClient: cpClient, + SystemConfig: cfg, + EventGatewayConfig: eventGatewayCfg, + HTTPClient: httpClient, + Logger: log, + WebhookSecretService: webhookSecretService, + DeploymentSearcher: apiServer, + EventHub: eventHubInstance, + GatewayID: gatewayID, + }) + + if err := igw.LoadArtifacts(log); err != nil { + log.Error("Failed to load immutable gateway artifacts", slog.Any("error", err)) + os.Exit(1) + } + + if lazyResourceStore.Count() > 0 { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + if err := lazyResourceSnapshotManager.UpdateSnapshot(ctx); err != nil { + log.Warn("Failed to generate initial lazy resource snapshot", slog.Any("error", err)) + } + cancel() + } + + mux := http.NewServeMux() + + // Core management routes (versioned + legacy), identical to gateway-controller. + api.HandlerWithOptions(apiServer, api.StdHTTPServerOptions{ + BaseURL: managementAPIBasePath, + BaseRouter: mux, + Middlewares: append(perRouteMiddlewares, igw.Middleware()), + }) + api.HandlerWithOptions(apiServer, api.StdHTTPServerOptions{ + BaseURL: "", + BaseRouter: mux, + Middlewares: append(perRouteMiddlewares, igw.Middleware(), deprecatedManagementPathMiddleware(managementAPIBasePath)), + }) + + // WebSub/WebBroker routes (versioned + legacy), from this module's own spec. + eventgateway.HandlerWithOptions(eventGatewayHandler, eventgateway.StdHTTPServerOptions{ + BaseURL: managementAPIBasePath, + BaseRouter: mux, + Middlewares: eventGatewayPerRouteMiddlewares, + }) + eventgateway.HandlerWithOptions(eventGatewayHandler, eventgateway.StdHTTPServerOptions{ + BaseURL: "", + BaseRouter: mux, + Middlewares: append(eventGatewayPerRouteMiddlewares, deprecatedManagementPathMiddleware(managementAPIBasePath)), + }) + + outerMiddlewares := []func(http.Handler) http.Handler{ + middleware.CorrelationIDMiddleware(log), + middleware.ErrorHandlingMiddleware(log), + middleware.LoggingMiddleware(log), + } + if cfg.Controller.Metrics.Enabled { + outerMiddlewares = append(outerMiddlewares, middleware.MetricsMiddleware()) + } + httpHandler := gohttpkit.Chain(outerMiddlewares...)(mux) + + // Enable block/mutex profiling sampling when pprof is enabled. These are the + // only profiles that need explicit rate setup; 0 leaves them disabled. Gated so + // the sampling overhead is never paid unless pprof is deliberately turned on. + if cfg.Controller.AdminServer.Pprof.Enabled { + runtime.SetBlockProfileRate(cfg.Controller.AdminServer.Pprof.BlockProfileRate) + runtime.SetMutexProfileFraction(cfg.Controller.AdminServer.Pprof.MutexProfileFraction) + } + + // Start controller admin server for debug endpoints if enabled. + var controllerAdminServer *adminserver.Server + if cfg.Controller.AdminServer.Enabled { + controllerAdminServer = adminserver.NewServer(&cfg.Controller.AdminServer, apiServer, log) + go func() { + if err := controllerAdminServer.Start(); err != nil { + log.Error("Controller admin server failed", slog.Any("error", err)) + os.Exit(1) + } + }() + } + + var metricsServer *metrics.Server + var metricsCtxCancel context.CancelFunc + if cfg.Controller.Metrics.Enabled { + metrics.Info.WithLabelValues(coreversion.Version, cfg.Controller.Storage.Type, coreversion.BuildDate).Set(1) + metricsServer = metrics.NewServer(&cfg.Controller.Metrics, log) + if err := metricsServer.Start(); err != nil { + log.Error("Metrics server failed", slog.Any("error", err)) + os.Exit(1) + } + var metricsCtx context.Context + metricsCtx, metricsCtxCancel = context.WithCancel(context.Background()) + metrics.StartMemoryMetricsUpdater(metricsCtx, 15*time.Second) + } + + srv := &http.Server{ + Addr: fmt.Sprintf(":%d", cfg.Controller.Server.APIPort), + Handler: httpHandler, + ReadHeaderTimeout: 30 * time.Second, + } + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error("Failed to start REST API server", slog.Any("error", err)) + os.Exit(1) + } + }() + + log.Info("Event-Gateway-Controller started successfully") + + go func() { + <-routerConnected + <-policyEngineConnected + time.Sleep(1 * time.Second) + fmt.Print("\n\n" + + "========================================================================\n" + + "\n" + + " API Platform Event-Gateway-Controller Started\n" + + "\n" + + "========================================================================\n" + + "\n\n") + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + log.Info("Shutting down Event-Gateway-Controller") + + ctx, cancel = context.WithTimeout(context.Background(), cfg.Controller.Server.ShutdownTimeout) + defer cancel() + + evtListener.Stop() + if err := eventHubInstance.Close(); err != nil { + log.Warn("Failed to close EventHub cleanly", slog.Any("error", err)) + } + if eventHubStorage != nil { + if err := eventHubStorage.Close(); err != nil { + log.Warn("Failed to close EventHub storage cleanly", slog.Any("error", err)) + } + } + + cpClient.Stop() + + if err := srv.Shutdown(ctx); err != nil { + log.Error("Server forced to shutdown", slog.Any("error", err)) + } + + xdsServer.Stop() + if policyXDSServer != nil { + policyXDSServer.Stop() + } + + if metricsServer != nil { + if metricsCtxCancel != nil { + metricsCtxCancel() + } + if err := metricsServer.Stop(ctx); err != nil { + log.Error("Failed to stop metrics server", slog.Any("error", err)) + } + } + + if controllerAdminServer != nil { + if err := controllerAdminServer.Stop(ctx); err != nil { + log.Error("Failed to stop controller admin server", slog.Any("error", err)) + } + } + + log.Info("Event-Gateway-Controller stopped") +} + +func generateAuthConfig(cfg *coreconfig.Config) commonmodels.AuthConfig { + prefixed := func(methodAndPath string) string { + idx := strings.Index(methodAndPath, " ") + if idx < 0 { + return methodAndPath + } + return methodAndPath[:idx+1] + managementAPIBasePath + methodAndPath[idx+1:] + } + + relativeRoles := map[string][]string{ + "POST /websub-apis": {"admin", "developer"}, + "GET /websub-apis": {"admin", "developer"}, + "GET /websub-apis/{id}": {"admin", "developer"}, + "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"}, + + "POST /websub-apis/{id}/api-keys": {"admin", "consumer"}, + "GET /websub-apis/{id}/api-keys": {"admin", "consumer"}, + "PUT /websub-apis/{id}/api-keys/{apiKeyName}": {"admin", "consumer"}, + "POST /websub-apis/{id}/api-keys/{apiKeyName}/regenerate": {"admin", "consumer"}, + "DELETE /websub-apis/{id}/api-keys/{apiKeyName}": {"admin", "consumer"}, + + "POST /websub-apis/{id}/secrets": {"admin", "developer"}, + "GET /websub-apis/{id}/secrets": {"admin", "developer"}, + "DELETE /websub-apis/{id}/secrets/{secretName}": {"admin", "developer"}, + "POST /websub-apis/{id}/secrets/{secretName}/regenerate": {"admin", "developer"}, + + "POST /webbroker-apis/{id}/api-keys": {"admin", "consumer"}, + "GET /webbroker-apis/{id}/api-keys": {"admin", "consumer"}, + "PUT /webbroker-apis/{id}/api-keys/{apiKeyName}": {"admin", "consumer"}, + "POST /webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate": {"admin", "consumer"}, + "DELETE /webbroker-apis/{id}/api-keys/{apiKeyName}": {"admin", "consumer"}, + } + + DefaultResourceRoles := make(map[string][]string, len(relativeRoles)*2) + for methodAndPath, roles := range relativeRoles { + DefaultResourceRoles[prefixed(methodAndPath)] = roles + DefaultResourceRoles[methodAndPath] = roles + } + basicAuth := commonmodels.BasicAuth{Enabled: false} + idpAuth := commonmodels.IDPConfig{Enabled: false} + if cfg.Controller.Auth.Basic.Enabled { + users := make([]commonmodels.User, len(cfg.Controller.Auth.Basic.Users)) + for i, authUser := range cfg.Controller.Auth.Basic.Users { + users[i] = commonmodels.User{ + Username: authUser.Username, + Password: authUser.Password, + PasswordHashed: authUser.PasswordHashed, + Roles: authUser.Roles, + } + } + basicAuth = commonmodels.BasicAuth{Enabled: true, Users: users} + } + if cfg.Controller.Auth.IDP.Enabled { + idpAuth = commonmodels.IDPConfig{ + Enabled: true, + IssuerURL: cfg.Controller.Auth.IDP.Issuer, + JWKSUrl: cfg.Controller.Auth.IDP.JWKSURL, + ScopeClaim: cfg.Controller.Auth.IDP.RolesClaim, + PermissionMapping: &cfg.Controller.Auth.IDP.RoleMapping, + } + } + return commonmodels.AuthConfig{ + BasicAuth: &basicAuth, + JWTConfig: &idpAuth, + ResourceRoles: DefaultResourceRoles, + } +} + +// deprecatedManagementPathMiddleware marks responses served on the legacy +// unprefixed management API paths as deprecated (RFC 8594). Mirrors core's +// gateway-controller/cmd/controller/main.go implementation exactly. +func deprecatedManagementPathMiddleware(newBasePath string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + successor := newBasePath + r.URL.Path + w.Header().Set("Deprecation", "true") + w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"successor-version\"", successor)) + w.Header().Set("Warning", fmt.Sprintf("299 - \"Deprecated API: migrate to %s prefix\"", newBasePath)) + next.ServeHTTP(w, r) + }) + } +} diff --git a/event-gateway/gateway-controller/cmd/controller/runtime_bootstrap.go b/event-gateway/gateway-controller/cmd/controller/runtime_bootstrap.go new file mode 100644 index 0000000000..39afc777ca --- /dev/null +++ b/event-gateway/gateway-controller/cmd/controller/runtime_bootstrap.go @@ -0,0 +1,194 @@ +/* + * 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. + */ + +// Copied from gateway-controller (core) cmd/controller/runtime_bootstrap.go — +// unexported helpers in package main can't be imported across module +// boundaries. Generic startup hydration for RestApi/Mcp/LlmProvider/LlmProxy +// kinds only (WebSubApi/WebBrokerApi are excluded here exactly as in core; +// this binary's own kinds don't need runtime-config bootstrap hydration). +package main + +import ( + "fmt" + "log/slog" + + api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" + "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/templateengine" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/templateengine/funcs" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" +) + +func hydrateStoredConfigsFromDatabaseOnStartup( + configStore *storage.ConfigStore, + db storage.Storage, + routerConfig *config.RouterConfig, + policyDefinitions map[string]models.PolicyDefinition, + log *slog.Logger, + skipInvalidDeployments bool, +) error { + if configStore == nil { + return nil + } + + if err := hydrateConfigsByKindForStartup( + configStore.GetAllByKind(string(api.MCPProxyConfigurationKindMcp)), + "stored MCP proxy configuration", + log, + skipInvalidDeployments, + utils.HydrateStoredMCPConfig, + ); err != nil { + return err + } + + if err := hydrateConfigsByKindForStartup( + configStore.GetAllByKind(string(api.LLMProviderConfigurationKindLlmProvider)), + "stored LLM provider configuration", + log, + skipInvalidDeployments, + func(cfg *models.StoredConfig) error { + return utils.HydrateLLMConfig(cfg, configStore, db, routerConfig, policyDefinitions) + }, + ); err != nil { + return err + } + + return hydrateConfigsByKindForStartup( + configStore.GetAllByKind(string(api.LLMProxyConfigurationKindLlmProxy)), + "stored LLM proxy configuration", + log, + skipInvalidDeployments, + func(cfg *models.StoredConfig) error { + return utils.HydrateLLMConfig(cfg, configStore, db, routerConfig, policyDefinitions) + }, + ) +} + +func hydrateConfigsByKindForStartup( + configs []*models.StoredConfig, + description string, + log *slog.Logger, + skipInvalidDeployments bool, + hydrate func(*models.StoredConfig) error, +) error { + for _, storedCfg := range configs { + if err := hydrate(storedCfg); err != nil { + if log != nil { + logFn := log.Error + message := "Failed to hydrate " + description + if skipInvalidDeployments { + logFn = log.Warn + message = "Skipping invalid " + description + " during startup" + } + logFn(message, + slog.String("id", storedCfg.UUID), + slog.String("handle", storedCfg.Handle), + slog.Any("error", err)) + } + if !skipInvalidDeployments { + return fmt.Errorf("failed to hydrate %s: %w", description, err) + } + } + } + + return nil +} + +func loadRuntimeConfigsFromExistingAPIConfigurations( + loadedConfigs []*models.StoredConfig, + runtimeStore *storage.RuntimeConfigStore, + secretResolver funcs.SecretResolver, + transformer models.ConfigTransformer, + log *slog.Logger, + skipInvalidDeployments bool, +) (int, error) { + if runtimeStore == nil || transformer == nil { + return 0, nil + } + + loadedCount := 0 + for _, apiConfig := range loadedConfigs { + if apiConfig == nil || !supportsRuntimeBootstrapKind(apiConfig.Kind) { + continue + } + + if secretResolver != nil { + if err := templateengine.RenderSpec(apiConfig, secretResolver, log); err != nil { + if log != nil { + if skipInvalidDeployments { + log.Warn("Template rendering failed during startup load, skipping", + slog.String("api_id", apiConfig.UUID), + slog.Any("error", err), + ) + } else { + log.Error("Template rendering failed during startup load", + slog.String("api_id", apiConfig.UUID), + slog.Any("error", err), + ) + } + } + if skipInvalidDeployments { + continue + } + return loadedCount, fmt.Errorf("failed to render config for startup %s: %w", apiConfig.UUID, err) + } + } + + rdc, err := transformer.Transform(apiConfig) + if err != nil { + if log != nil { + if skipInvalidDeployments { + log.Warn("Failed to transform API config at startup", + slog.String("api_id", apiConfig.UUID), + slog.String("kind", apiConfig.Kind), + slog.Any("error", err)) + } else { + log.Error("Failed to transform API config at startup", + slog.String("api_id", apiConfig.UUID), + slog.String("kind", apiConfig.Kind), + slog.Any("error", err)) + } + } + if skipInvalidDeployments { + continue + } + return loadedCount, fmt.Errorf( + "failed to transform startup config %s (%s): %w", + apiConfig.UUID, + apiConfig.Kind, + err, + ) + } + + runtimeStore.Set(storage.Key(apiConfig.Kind, apiConfig.Handle), rdc) + loadedCount++ + } + + return loadedCount, nil +} + +func supportsRuntimeBootstrapKind(kind string) bool { + switch kind { + case models.KindRestApi, models.KindMcp, models.KindLlmProvider, models.KindLlmProxy: + return true + default: + return false + } +} diff --git a/event-gateway/gateway-controller/go.mod b/event-gateway/gateway-controller/go.mod new file mode 100644 index 0000000000..ae50b76b1c --- /dev/null +++ b/event-gateway/gateway-controller/go.mod @@ -0,0 +1,88 @@ +module github.com/wso2/api-platform/event-gateway/gateway-controller + +go 1.26.5 + +require ( + github.com/envoyproxy/go-control-plane v0.14.0 + github.com/envoyproxy/go-control-plane/envoy v1.36.0 + github.com/getkin/kin-openapi v0.133.0 + github.com/google/uuid v1.6.0 + github.com/knadh/koanf/parsers/toml/v2 v2.2.0 + github.com/knadh/koanf/providers/file v1.2.1 + github.com/knadh/koanf/v2 v2.3.2 + github.com/oapi-codegen/runtime v1.5.0 + github.com/wso2/api-platform/common v0.0.0 + github.com/wso2/api-platform/gateway/gateway-controller v0.0.0 + github.com/wso2/api-platform/sdk/core v0.2.18 + github.com/wso2/go-httpkit v0.0.0-local + google.golang.org/protobuf v1.36.11 +) + +require ( + cel.dev/expr v0.25.1 // indirect + github.com/MicahParks/jwkset v0.11.0 // indirect + github.com/MicahParks/keyfunc/v3 v3.7.0 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/knadh/koanf/maps v0.1.2 // indirect + github.com/knadh/koanf/providers/confmap v1.0.0 // indirect + github.com/knadh/koanf/providers/env v1.1.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-sqlite3 v1.14.41 // indirect + github.com/microsoft/go-mssqldb v1.10.0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // 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.19.2 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + 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 + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/time v0.14.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/grpc v1.79.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/wso2/api-platform/common => ../../common + +replace github.com/wso2/go-httpkit => ../../httpkit + +replace github.com/wso2/api-platform/gateway/gateway-controller => ../../gateway/gateway-controller diff --git a/event-gateway/gateway-controller/go.sum b/event-gateway/gateway-controller/go.sum new file mode 100644 index 0000000000..c78629b94b --- /dev/null +++ b/event-gateway/gateway-controller/go.sum @@ -0,0 +1,218 @@ +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ= +github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0= +github.com/MicahParks/keyfunc/v3 v3.7.0 h1:pdafUNyq+p3ZlvjJX1HWFP7MA3+cLpDtg69U3kITJGM= +github.com/MicahParks/keyfunc/v3 v3.7.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +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/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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.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= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +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/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/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= +github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A= +github.com/knadh/koanf/parsers/toml/v2 v2.2.0/go.mod h1:JpjTeK1Ge1hVX0wbof5DMCuDBriR8bWgeQP98eeOZpI= +github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE= +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.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= +github.com/knadh/koanf/v2 v2.3.2 h1:Ee6tuzQYFwcZXQpc2MiVeC6qHMandf5SMUJJNoFp/c4= +github.com/knadh/koanf/v2 v2.3.2/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +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.41/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY= +github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= +github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= +github.com/oapi-codegen/runtime v1.5.0 h1:aiil4QnH+eiWYSO60eaYZ4aur7sJH3rz6BvT5EBFnxc= +github.com/oapi-codegen/runtime v1.5.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +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/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +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/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +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/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/wso2/api-platform/sdk/core v0.2.18 h1:j6UhHjGBa9qCxqbT4p8cwLUKkQVtpvoMTMsjs1DfYR0= +github.com/wso2/api-platform/sdk/core v0.2.18/go.mod h1:NZMDIQadQDpbynlCLYdZT6duiHwnf7emr7SbLHdCjaE= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +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.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +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/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.11/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= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= diff --git a/event-gateway/gateway-controller/oapi-codegen.yaml b/event-gateway/gateway-controller/oapi-codegen.yaml new file mode 100644 index 0000000000..0f0b90335e --- /dev/null +++ b/event-gateway/gateway-controller/oapi-codegen.yaml @@ -0,0 +1,9 @@ +package: eventgateway +output: pkg/api/eventgateway/generated.go +generate: + std-http-server: true + models: true + embedded-spec: true + strict-server: false +output-options: + yaml-tags: true diff --git a/event-gateway/gateway-controller/pkg/api/eventgateway/generated.go b/event-gateway/gateway-controller/pkg/api/eventgateway/generated.go new file mode 100644 index 0000000000..1e058c951e --- /dev/null +++ b/event-gateway/gateway-controller/pkg/api/eventgateway/generated.go @@ -0,0 +1,1917 @@ +//go:build go1.22 + +// Package eventgateway provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT. +package eventgateway + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + "time" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/runtime" +) + +const ( + BasicAuthScopes = "basicAuth.Scopes" +) + +// Defines values for APIKeySource. +const ( + External APIKeySource = "external" + Local APIKeySource = "local" +) + +// Defines values for APIKeyStatus. +const ( + APIKeyStatusActive APIKeyStatus = "active" + APIKeyStatusExpired APIKeyStatus = "expired" + APIKeyStatusRevoked APIKeyStatus = "revoked" +) + +// Defines values for APIKeyCreationRequestExpiresInUnit. +const ( + APIKeyCreationRequestExpiresInUnitDays APIKeyCreationRequestExpiresInUnit = "days" + APIKeyCreationRequestExpiresInUnitHours APIKeyCreationRequestExpiresInUnit = "hours" + APIKeyCreationRequestExpiresInUnitMinutes APIKeyCreationRequestExpiresInUnit = "minutes" + APIKeyCreationRequestExpiresInUnitMonths APIKeyCreationRequestExpiresInUnit = "months" + APIKeyCreationRequestExpiresInUnitSeconds APIKeyCreationRequestExpiresInUnit = "seconds" + APIKeyCreationRequestExpiresInUnitWeeks APIKeyCreationRequestExpiresInUnit = "weeks" +) + +// Defines values for APIKeyRegenerationRequestExpiresInUnit. +const ( + APIKeyRegenerationRequestExpiresInUnitDays APIKeyRegenerationRequestExpiresInUnit = "days" + APIKeyRegenerationRequestExpiresInUnitHours APIKeyRegenerationRequestExpiresInUnit = "hours" + APIKeyRegenerationRequestExpiresInUnitMinutes APIKeyRegenerationRequestExpiresInUnit = "minutes" + APIKeyRegenerationRequestExpiresInUnitMonths APIKeyRegenerationRequestExpiresInUnit = "months" + APIKeyRegenerationRequestExpiresInUnitSeconds APIKeyRegenerationRequestExpiresInUnit = "seconds" + APIKeyRegenerationRequestExpiresInUnitWeeks APIKeyRegenerationRequestExpiresInUnit = "weeks" +) + +// Defines values for ResourceStatusState. +const ( + ResourceStatusStateDeployed ResourceStatusState = "deployed" + ResourceStatusStateUndeployed ResourceStatusState = "undeployed" +) + +// Defines values for WebBrokerApiApiVersion. +const ( + WebBrokerApiApiVersionGatewayApiPlatformWso2Comv1 WebBrokerApiApiVersion = "gateway.api-platform.wso2.com/v1" +) + +// Defines values for WebBrokerApiKind. +const ( + WebBrokerApiKindWebBrokerApi WebBrokerApiKind = "WebBrokerApi" +) + +// Defines values for WebBrokerApiDataDeploymentState. +const ( + WebBrokerApiDataDeploymentStateDeployed WebBrokerApiDataDeploymentState = "deployed" + WebBrokerApiDataDeploymentStateUndeployed WebBrokerApiDataDeploymentState = "undeployed" +) + +// Defines values for WebBrokerApiRequestApiVersion. +const ( + WebBrokerApiRequestApiVersionGatewayApiPlatformWso2Comv1 WebBrokerApiRequestApiVersion = "gateway.api-platform.wso2.com/v1" +) + +// Defines values for WebBrokerApiRequestKind. +const ( + WebBrokerApiRequestKindWebBrokerApi WebBrokerApiRequestKind = "WebBrokerApi" +) + +// Defines values for WebSubAPIApiVersion. +const ( + WebSubAPIApiVersionGatewayApiPlatformWso2Comv1 WebSubAPIApiVersion = "gateway.api-platform.wso2.com/v1" +) + +// Defines values for WebSubAPIKind. +const ( + WebSubAPIKindWebSubApi WebSubAPIKind = "WebSubApi" +) + +// Defines values for WebSubAPIRequestApiVersion. +const ( + WebSubAPIRequestApiVersionGatewayApiPlatformWso2Comv1 WebSubAPIRequestApiVersion = "gateway.api-platform.wso2.com/v1" +) + +// Defines values for WebSubAPIRequestKind. +const ( + WebSubAPIRequestKindWebSubApi WebSubAPIRequestKind = "WebSubApi" +) + +// Defines values for WebhookAPIDataDeploymentState. +const ( + WebhookAPIDataDeploymentStateDeployed WebhookAPIDataDeploymentState = "deployed" + WebhookAPIDataDeploymentStateUndeployed WebhookAPIDataDeploymentState = "undeployed" +) + +// Defines values for WebhookSecretInfoStatus. +const ( + WebhookSecretInfoStatusActive WebhookSecretInfoStatus = "active" + WebhookSecretInfoStatusRevoked WebhookSecretInfoStatus = "revoked" +) + +// Defines values for ListWebBrokerApisParamsStatus. +const ( + ListWebBrokerApisParamsStatusDeployed ListWebBrokerApisParamsStatus = "deployed" + ListWebBrokerApisParamsStatusUndeployed ListWebBrokerApisParamsStatus = "undeployed" +) + +// Defines values for ListWebSubAPIsParamsStatus. +const ( + Deployed ListWebSubAPIsParamsStatus = "deployed" + Undeployed ListWebSubAPIsParamsStatus = "undeployed" +) + +// APIKey Details of an API key +type APIKey struct { + // ApiId Unique public identifier of the API that the key is associated with + ApiId string `json:"apiId" yaml:"apiId"` + + // ApiKey Generated API key with apip_ prefix + ApiKey *string `json:"apiKey,omitempty" yaml:"apiKey,omitempty"` + + // CreatedAt Timestamp when the API key was generated + CreatedAt time.Time `json:"createdAt" yaml:"createdAt"` + + // CreatedBy Identifier of the user who generated the API key + CreatedBy string `json:"createdBy" yaml:"createdBy"` + + // DisplayName Human-readable name for the API key (user-provided, mutable) + DisplayName *string `json:"displayName,omitempty" yaml:"displayName,omitempty"` + + // ExpiresAt Expiration timestamp (null if no expiration) + ExpiresAt *time.Time `json:"expiresAt" yaml:"expiresAt"` + + // ExternalRefId External reference ID for the API key + ExternalRefId *string `json:"externalRefId,omitempty" yaml:"externalRefId,omitempty"` + + // Name URL-safe identifier for the API key (auto-generated from displayName, immutable, used as path parameter) + Name string `json:"name" yaml:"name"` + + // Source Source of the API key (local or external) + Source APIKeySource `json:"source" yaml:"source"` + + // Status Status of the API key + Status APIKeyStatus `json:"status" yaml:"status"` +} + +// APIKeySource Source of the API key (local or external) +type APIKeySource string + +// APIKeyStatus Status of the API key +type APIKeyStatus string + +// APIKeyCreationRequest defines model for APIKeyCreationRequest. +type APIKeyCreationRequest struct { + // ApiKey Optional plain-text API key value for external key injection. + // If provided, this key will be used instead of generating a new one. + // The key will be hashed before storage. The key can be in any format + // (minimum 36 characters). Use this for injecting externally generated + // API keys. + ApiKey *string `json:"apiKey,omitempty" yaml:"apiKey,omitempty"` + + // ExpiresAt Expiration timestamp. If both expiresIn and expiresAt are provided, expiresAt takes precedence. + ExpiresAt *time.Time `json:"expiresAt,omitempty" yaml:"expiresAt,omitempty"` + + // ExpiresIn Expiration duration for the API key + ExpiresIn *struct { + // Duration Duration value for expiration + Duration int `json:"duration" yaml:"duration"` + + // Unit Time unit for expiration + Unit APIKeyCreationRequestExpiresInUnit `json:"unit" yaml:"unit"` + } `json:"expiresIn,omitempty" yaml:"expiresIn,omitempty"` + + // ExternalRefId External reference ID for the API key. + // This field is optional and used for tracing purposes only. + // The gateway generates its own internal ID for tracking. + ExternalRefId *string `json:"externalRefId,omitempty" yaml:"externalRefId,omitempty"` + + // Issuer Identifies the portal that created this key. If provided, only api keys generated from + // the same portal will be accepted. If not provided, there is no portal restriction. + Issuer *string `json:"issuer,omitempty" yaml:"issuer,omitempty"` + + // MaskedApiKey Masked version of the API key for display purposes. + // Provided by the platform API when injecting pre-hashed keys. + MaskedApiKey *string `json:"maskedApiKey,omitempty" yaml:"maskedApiKey,omitempty"` + + // Name Identifier of the API key. If not provided, a default identifier will be generated + Name *string `json:"name,omitempty" yaml:"name,omitempty"` +} + +// APIKeyCreationRequestExpiresInUnit Time unit for expiration +type APIKeyCreationRequestExpiresInUnit string + +// APIKeyCreationResponse defines model for APIKeyCreationResponse. +type APIKeyCreationResponse struct { + // ApiKey Details of an API key + ApiKey *APIKey `json:"apiKey,omitempty" yaml:"apiKey,omitempty"` + Message string `json:"message" yaml:"message"` + + // RemainingApiKeyQuota Remaining API key quota for the user + RemainingApiKeyQuota *int `json:"remainingApiKeyQuota,omitempty" yaml:"remainingApiKeyQuota,omitempty"` + Status string `json:"status" yaml:"status"` +} + +// APIKeyListResponse defines model for APIKeyListResponse. +type APIKeyListResponse struct { + ApiKeys *[]APIKey `json:"apiKeys,omitempty" yaml:"apiKeys,omitempty"` + Status *string `json:"status,omitempty" yaml:"status,omitempty"` + + // TotalCount Total number of API keys + TotalCount *int `json:"totalCount,omitempty" yaml:"totalCount,omitempty"` +} + +// APIKeyRegenerationRequest defines model for APIKeyRegenerationRequest. +type APIKeyRegenerationRequest struct { + // ExpiresAt Expiration timestamp + ExpiresAt *time.Time `json:"expiresAt,omitempty" yaml:"expiresAt,omitempty"` + + // ExpiresIn Expiration duration for the API key + ExpiresIn *struct { + // Duration Duration value for expiration + Duration int `json:"duration" yaml:"duration"` + + // Unit Time unit for expiration + Unit APIKeyRegenerationRequestExpiresInUnit `json:"unit" yaml:"unit"` + } `json:"expiresIn,omitempty" yaml:"expiresIn,omitempty"` +} + +// APIKeyRegenerationRequestExpiresInUnit Time unit for expiration +type APIKeyRegenerationRequestExpiresInUnit string + +// APIKeyRevocationResponse defines model for APIKeyRevocationResponse. +type APIKeyRevocationResponse struct { + Message string `json:"message" yaml:"message"` + Status string `json:"status" yaml:"status"` +} + +// APIKeyUpdateRequest defines model for APIKeyUpdateRequest. +type APIKeyUpdateRequest = APIKeyCreationRequest + +// ErrorResponse defines model for ErrorResponse. +type ErrorResponse struct { + // Errors Detailed validation errors + Errors *[]ValidationError `json:"errors,omitempty" yaml:"errors,omitempty"` + + // Message High-level error description + Message string `json:"message" yaml:"message"` + Status string `json:"status" yaml:"status"` +} + +// Metadata defines model for Metadata. +type Metadata struct { + // Annotations Annotations are arbitrary non-identifying metadata. Use domain-prefixed keys. + Annotations *map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` + + // Labels Labels are key-value pairs for organizing and selecting APIs. Keys must not contain spaces. + Labels *map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + + // Name Unique handle for the resource + Name string `json:"name" yaml:"name"` +} + +// Policy defines model for Policy. +type Policy struct { + // ExecutionCondition Expression controlling conditional execution of the policy + ExecutionCondition *string `json:"executionCondition,omitempty" yaml:"executionCondition,omitempty"` + + // Name Name of the policy + Name string `json:"name" yaml:"name"` + + // Params Arbitrary parameters for the policy (free-form key/value structure) + Params *map[string]interface{} `json:"params,omitempty" yaml:"params,omitempty"` + + // Version 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. + Version string `json:"version" yaml:"version"` +} + +// ResourceStatus Server-managed lifecycle information for a resource +type ResourceStatus struct { + // CreatedAt Timestamp when the resource was first created (UTC) + CreatedAt *time.Time `json:"createdAt,omitempty" yaml:"createdAt,omitempty"` + + // DeployedAt Timestamp when the resource was last deployed (omitted when undeployed) + DeployedAt *time.Time `json:"deployedAt,omitempty" yaml:"deployedAt,omitempty"` + + // Id Unique identifier assigned by the server (equal to metadata.name) + Id *string `json:"id,omitempty" yaml:"id,omitempty"` + + // State Desired deployment state reported by the server + State *ResourceStatusState `json:"state,omitempty" yaml:"state,omitempty"` + + // UpdatedAt Timestamp when the resource was last updated (UTC) + UpdatedAt *time.Time `json:"updatedAt,omitempty" yaml:"updatedAt,omitempty"` +} + +// ResourceStatusState Desired deployment state reported by the server +type ResourceStatusState string + +// ValidationError defines model for ValidationError. +type ValidationError struct { + // Field Field that failed validation + Field *string `json:"field,omitempty" yaml:"field,omitempty"` + + // Message Human-readable error message + 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 + +// 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"` + + // 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"` + + // 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"` + + // 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 +type WebBrokerApiConsumeConfig struct { + // Topic Kafka topic to consume messages from + Topic string `json:"topic" yaml:"topic"` +} + +// WebBrokerApiData defines model for WebBrokerApiData. +type WebBrokerApiData struct { + // 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"` + + // 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"` + + // 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 + +// 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 +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 + ApiVersion WebSubAPIApiVersion `json:"apiVersion" yaml:"apiVersion"` + + // Kind API type + Kind WebSubAPIKind `json:"kind" yaml:"kind"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec WebhookAPIData `json:"spec" yaml:"spec"` + + // Status Server-managed lifecycle fields. Populated on responses. + Status *ResourceStatus `json:"status,omitempty" yaml:"status,omitempty"` +} + +// WebSubAPIApiVersion API specification version +type WebSubAPIApiVersion string + +// WebSubAPIKind API type +type WebSubAPIKind string + +// WebSubAPIRequest defines model for WebSubAPIRequest. +type WebSubAPIRequest struct { + // ApiVersion API specification version + ApiVersion WebSubAPIRequestApiVersion `json:"apiVersion" yaml:"apiVersion"` + + // Kind API type + Kind WebSubAPIRequestKind `json:"kind" yaml:"kind"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec WebhookAPIData `json:"spec" yaml:"spec"` +} + +// WebSubAPIRequestApiVersion API specification version +type WebSubAPIRequestApiVersion string + +// WebSubAPIRequestKind API type +type WebSubAPIRequestKind string + +// WebSubAllChannelPolicies Policies applied to all channels, organized by event type. +type WebSubAllChannelPolicies struct { + // OnMessageDelivery Policies for a single event type. + OnMessageDelivery *WebSubEventPolicies `json:"on_message_delivery,omitempty" yaml:"on_message_delivery,omitempty"` + + // OnMessageReceived Policies for a single event type. + OnMessageReceived *WebSubEventPolicies `json:"on_message_received,omitempty" yaml:"on_message_received,omitempty"` + + // OnSubscription Policies for a single event type. + OnSubscription *WebSubEventPolicies `json:"on_subscription,omitempty" yaml:"on_subscription,omitempty"` + + // OnUnsubscription Policies for a single event type. + OnUnsubscription *WebSubEventPolicies `json:"on_unsubscription,omitempty" yaml:"on_unsubscription,omitempty"` +} + +// WebSubChannel A single channel definition with optional per-channel policy overrides. +type WebSubChannel struct { + // OnMessageDelivery Policies for a single event type. + OnMessageDelivery *WebSubEventPolicies `json:"on_message_delivery,omitempty" yaml:"on_message_delivery,omitempty"` + + // OnMessageReceived Policies for a single event type. + OnMessageReceived *WebSubEventPolicies `json:"on_message_received,omitempty" yaml:"on_message_received,omitempty"` + + // OnSubscription Policies for a single event type. + OnSubscription *WebSubEventPolicies `json:"on_subscription,omitempty" yaml:"on_subscription,omitempty"` + + // OnUnsubscription Policies for a single event type. + OnUnsubscription *WebSubEventPolicies `json:"on_unsubscription,omitempty" yaml:"on_unsubscription,omitempty"` +} + +// WebSubEventPolicies Policies for a single event type. +type WebSubEventPolicies struct { + // Policies List of policies applied for this event type. + Policies *[]Policy `json:"policies,omitempty" yaml:"policies,omitempty"` +} + +// WebhookAPIData defines model for WebhookAPIData. +type WebhookAPIData struct { + // AllChannels Policies applied to all channels, organized by event type. + AllChannels *WebSubAllChannelPolicies `json:"allChannels,omitempty" yaml:"allChannels,omitempty"` + + // Channels Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel. + Channels *map[string]WebSubChannel `json:"channels,omitempty" yaml:"channels,omitempty"` + + // 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 *WebhookAPIDataDeploymentState `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"` + + // 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"` +} + +// WebhookAPIDataDeploymentState 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. +type WebhookAPIDataDeploymentState string + +// WebhookSecretCreationRequest defines model for WebhookSecretCreationRequest. +type WebhookSecretCreationRequest struct { + // DisplayName Human-readable label for this secret (used to derive the immutable name slug). + DisplayName string `json:"displayName" yaml:"displayName"` +} + +// WebhookSecretCreationResponse defines model for WebhookSecretCreationResponse. +type WebhookSecretCreationResponse struct { + Message string `json:"message" yaml:"message"` + + // Secret The generated plaintext secret value (whsec_ prefix + 64 hex chars). + // Returned exactly once — store it immediately as it will not be retrievable again. + Secret string `json:"secret" yaml:"secret"` + Status string `json:"status" yaml:"status"` + + // WebhookSecret Metadata for an HMAC secret. The plaintext value is never included. + WebhookSecret *WebhookSecretInfo `json:"webhookSecret,omitempty" yaml:"webhookSecret,omitempty"` +} + +// WebhookSecretInfo Metadata for an HMAC secret. The plaintext value is never included. +type WebhookSecretInfo struct { + CreatedAt *time.Time `json:"createdAt,omitempty" yaml:"createdAt,omitempty"` + + // DisplayName Human-readable label. + DisplayName *string `json:"displayName,omitempty" yaml:"displayName,omitempty"` + + // Name URL-safe slug (immutable, used as path parameter for regenerate/delete). + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + Status *WebhookSecretInfoStatus `json:"status,omitempty" yaml:"status,omitempty"` + UpdatedAt *time.Time `json:"updatedAt,omitempty" yaml:"updatedAt,omitempty"` +} + +// WebhookSecretInfoStatus defines model for WebhookSecretInfo.Status. +type WebhookSecretInfoStatus string + +// WebhookSecretListResponse defines model for WebhookSecretListResponse. +type WebhookSecretListResponse struct { + Secrets *[]WebhookSecretInfo `json:"secrets,omitempty" yaml:"secrets,omitempty"` + Status *string `json:"status,omitempty" yaml:"status,omitempty"` + + // TotalCount Total number of active secrets for this API + TotalCount *int `json:"totalCount,omitempty" yaml:"totalCount,omitempty"` +} + +// 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 + DisplayName *string `form:"displayName,omitempty" json:"displayName,omitempty" yaml:"displayName,omitempty"` + + // Version Filter by WebSub API version + Version *string `form:"version,omitempty" json:"version,omitempty" yaml:"version,omitempty"` + + // Context Filter by WebSub API context/path + Context *string `form:"context,omitempty" json:"context,omitempty" yaml:"context,omitempty"` + + // Status Filter by deployment status + Status *ListWebSubAPIsParamsStatus `form:"status,omitempty" json:"status,omitempty" yaml:"status,omitempty"` +} + +// ListWebSubAPIsParamsStatus defines parameters for ListWebSubAPIs. +type ListWebSubAPIsParamsStatus string + +// CreateWebBrokerApiJSONRequestBody defines body for CreateWebBrokerApi for application/json ContentType. +type CreateWebBrokerApiJSONRequestBody = WebBrokerApiRequest + +// CreateWebBrokerAPIKeyJSONRequestBody defines body for CreateWebBrokerAPIKey for application/json ContentType. +type CreateWebBrokerAPIKeyJSONRequestBody = APIKeyCreationRequest + +// UpdateWebBrokerAPIKeyJSONRequestBody defines body for UpdateWebBrokerAPIKey for application/json ContentType. +type UpdateWebBrokerAPIKeyJSONRequestBody = APIKeyUpdateRequest + +// RegenerateWebBrokerAPIKeyJSONRequestBody defines body for RegenerateWebBrokerAPIKey for application/json ContentType. +type RegenerateWebBrokerAPIKeyJSONRequestBody = APIKeyRegenerationRequest + +// CreateWebSubAPIJSONRequestBody defines body for CreateWebSubAPI for application/json ContentType. +type CreateWebSubAPIJSONRequestBody = WebSubAPIRequest + +// UpdateWebSubAPIJSONRequestBody defines body for UpdateWebSubAPI for application/json ContentType. +type UpdateWebSubAPIJSONRequestBody = WebSubAPIRequest + +// CreateWebSubAPIKeyJSONRequestBody defines body for CreateWebSubAPIKey for application/json ContentType. +type CreateWebSubAPIKeyJSONRequestBody = APIKeyCreationRequest + +// UpdateWebSubAPIKeyJSONRequestBody defines body for UpdateWebSubAPIKey for application/json ContentType. +type UpdateWebSubAPIKeyJSONRequestBody = APIKeyUpdateRequest + +// RegenerateWebSubAPIKeyJSONRequestBody defines body for RegenerateWebSubAPIKey for application/json ContentType. +type RegenerateWebSubAPIKeyJSONRequestBody = APIKeyRegenerationRequest + +// CreateWebSubAPISecretJSONRequestBody defines body for CreateWebSubAPISecret for application/json ContentType. +type CreateWebSubAPISecretJSONRequestBody = WebhookSecretCreationRequest + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // List all WebBrokerAPIs + // (GET /webbroker-apis) + ListWebBrokerApis(w http.ResponseWriter, r *http.Request, params ListWebBrokerApisParams) + // Create a new WebBrokerAPI + // (POST /webbroker-apis) + CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) + // Delete a WebBrokerAPI + // (DELETE /webbroker-apis/{id}) + DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) + // Get WebBrokerAPI by id + // (GET /webbroker-apis/{id}) + GetWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) + // Get the list of API keys for a WebBroker API + // (GET /webbroker-apis/{id}/api-keys) + ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request, id string) + // Create a new API key for a WebBroker API + // (POST /webbroker-apis/{id}/api-keys) + CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string) + // Revoke an API key for a WebBroker API + // (DELETE /webbroker-apis/{id}/api-keys/{apiKeyName}) + RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) + // Update an API key for a WebBroker API + // (PUT /webbroker-apis/{id}/api-keys/{apiKeyName}) + UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) + // Regenerate API key for a WebBroker API + // (POST /webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate) + RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) + // List all WebSubAPIs + // (GET /websub-apis) + ListWebSubAPIs(w http.ResponseWriter, r *http.Request, params ListWebSubAPIsParams) + // Create a new WebSubAPI + // (POST /websub-apis) + CreateWebSubAPI(w http.ResponseWriter, r *http.Request) + // Delete a WebSubAPI + // (DELETE /websub-apis/{id}) + DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id string) + // Get WebSubAPI by id + // (GET /websub-apis/{id}) + GetWebSubAPIById(w http.ResponseWriter, r *http.Request, id string) + // Update an existing WebSubAPI + // (PUT /websub-apis/{id}) + UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id string) + // Get the list of API keys for a WebSub API + // (GET /websub-apis/{id}/api-keys) + ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request, id string) + // Create a new API key for a WebSub API + // (POST /websub-apis/{id}/api-keys) + CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string) + // Revoke an API key for a WebSub API + // (DELETE /websub-apis/{id}/api-keys/{apiKeyName}) + RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) + // Update an API key for a WebSub API + // (PUT /websub-apis/{id}/api-keys/{apiKeyName}) + UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) + // Regenerate API key for a WebSub API + // (POST /websub-apis/{id}/api-keys/{apiKeyName}/regenerate) + RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) + // List HMAC secrets for a WebSub API + // (GET /websub-apis/{id}/secrets) + ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request, id string) + // Generate a new HMAC secret for a WebSub API + // (POST /websub-apis/{id}/secrets) + CreateWebSubAPISecret(w http.ResponseWriter, r *http.Request, id string) + // Delete a WebSub API HMAC secret + // (DELETE /websub-apis/{id}/secrets/{secretName}) + DeleteWebSubAPISecret(w http.ResponseWriter, r *http.Request, id string, secretName string) + // Regenerate (rotate) a WebSub API HMAC secret + // (POST /websub-apis/{id}/secrets/{secretName}/regenerate) + RegenerateWebSubAPISecret(w http.ResponseWriter, r *http.Request, id string, secretName string) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// ListWebBrokerApis operation middleware +func (siw *ServerInterfaceWrapper) ListWebBrokerApis(w http.ResponseWriter, r *http.Request) { + + var err error + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + // 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", r.URL.Query(), ¶ms.DisplayName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "displayName", Err: err}) + return + } + + // ------------- Optional query parameter "version" ------------- + + err = runtime.BindQueryParameter("form", true, false, "version", r.URL.Query(), ¶ms.Version) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "version", Err: err}) + return + } + + // ------------- Optional query parameter "status" ------------- + + err = runtime.BindQueryParameter("form", true, false, "status", r.URL.Query(), ¶ms.Status) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListWebBrokerApis(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateWebBrokerApi operation middleware +func (siw *ServerInterfaceWrapper) CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateWebBrokerApi(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteWebBrokerApiById operation middleware +func (siw *ServerInterfaceWrapper) DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteWebBrokerApiById(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetWebBrokerApiById operation middleware +func (siw *ServerInterfaceWrapper) GetWebBrokerApiById(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetWebBrokerApiById(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListWebBrokerAPIKeys operation middleware +func (siw *ServerInterfaceWrapper) ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListWebBrokerAPIKeys(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateWebBrokerAPIKey operation middleware +func (siw *ServerInterfaceWrapper) CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateWebBrokerAPIKey(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RevokeWebBrokerAPIKey operation middleware +func (siw *ServerInterfaceWrapper) RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "apiKeyName" ------------- + var apiKeyName string + + err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RevokeWebBrokerAPIKey(w, r, id, apiKeyName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UpdateWebBrokerAPIKey operation middleware +func (siw *ServerInterfaceWrapper) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "apiKeyName" ------------- + var apiKeyName string + + err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateWebBrokerAPIKey(w, r, id, apiKeyName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RegenerateWebBrokerAPIKey operation middleware +func (siw *ServerInterfaceWrapper) RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "apiKeyName" ------------- + var apiKeyName string + + err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RegenerateWebBrokerAPIKey(w, r, id, apiKeyName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListWebSubAPIs operation middleware +func (siw *ServerInterfaceWrapper) ListWebSubAPIs(w http.ResponseWriter, r *http.Request) { + + var err error + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + // Parameter object where we will unmarshal all parameters from the context + var params ListWebSubAPIsParams + + // ------------- Optional query parameter "displayName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "displayName", r.URL.Query(), ¶ms.DisplayName) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "displayName", Err: err}) + return + } + + // ------------- Optional query parameter "version" ------------- + + err = runtime.BindQueryParameter("form", true, false, "version", r.URL.Query(), ¶ms.Version) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "version", Err: err}) + return + } + + // ------------- Optional query parameter "context" ------------- + + err = runtime.BindQueryParameter("form", true, false, "context", r.URL.Query(), ¶ms.Context) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "context", Err: err}) + return + } + + // ------------- Optional query parameter "status" ------------- + + err = runtime.BindQueryParameter("form", true, false, "status", r.URL.Query(), ¶ms.Status) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListWebSubAPIs(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateWebSubAPI operation middleware +func (siw *ServerInterfaceWrapper) CreateWebSubAPI(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateWebSubAPI(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteWebSubAPI operation middleware +func (siw *ServerInterfaceWrapper) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteWebSubAPI(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetWebSubAPIById operation middleware +func (siw *ServerInterfaceWrapper) GetWebSubAPIById(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetWebSubAPIById(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UpdateWebSubAPI operation middleware +func (siw *ServerInterfaceWrapper) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateWebSubAPI(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListWebSubAPIKeys operation middleware +func (siw *ServerInterfaceWrapper) ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListWebSubAPIKeys(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateWebSubAPIKey operation middleware +func (siw *ServerInterfaceWrapper) CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateWebSubAPIKey(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RevokeWebSubAPIKey operation middleware +func (siw *ServerInterfaceWrapper) RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "apiKeyName" ------------- + var apiKeyName string + + err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RevokeWebSubAPIKey(w, r, id, apiKeyName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UpdateWebSubAPIKey operation middleware +func (siw *ServerInterfaceWrapper) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "apiKeyName" ------------- + var apiKeyName string + + err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateWebSubAPIKey(w, r, id, apiKeyName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RegenerateWebSubAPIKey operation middleware +func (siw *ServerInterfaceWrapper) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "apiKeyName" ------------- + var apiKeyName string + + err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RegenerateWebSubAPIKey(w, r, id, apiKeyName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListWebSubAPISecrets operation middleware +func (siw *ServerInterfaceWrapper) ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListWebSubAPISecrets(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateWebSubAPISecret operation middleware +func (siw *ServerInterfaceWrapper) CreateWebSubAPISecret(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateWebSubAPISecret(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteWebSubAPISecret operation middleware +func (siw *ServerInterfaceWrapper) DeleteWebSubAPISecret(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "secretName" ------------- + var secretName string + + err = runtime.BindStyledParameterWithOptions("simple", "secretName", r.PathValue("secretName"), &secretName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "secretName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteWebSubAPISecret(w, r, id, secretName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RegenerateWebSubAPISecret operation middleware +func (siw *ServerInterfaceWrapper) RegenerateWebSubAPISecret(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "secretName" ------------- + var secretName string + + err = runtime.BindStyledParameterWithOptions("simple", "secretName", r.PathValue("secretName"), &secretName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "secretName", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RegenerateWebSubAPISecret(w, r, id, secretName) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/webbroker-apis", wrapper.ListWebBrokerApis) + m.HandleFunc("POST "+options.BaseURL+"/webbroker-apis", wrapper.CreateWebBrokerApi) + m.HandleFunc("DELETE "+options.BaseURL+"/webbroker-apis/{id}", wrapper.DeleteWebBrokerApiById) + m.HandleFunc("GET "+options.BaseURL+"/webbroker-apis/{id}", wrapper.GetWebBrokerApiById) + m.HandleFunc("GET "+options.BaseURL+"/webbroker-apis/{id}/api-keys", wrapper.ListWebBrokerAPIKeys) + m.HandleFunc("POST "+options.BaseURL+"/webbroker-apis/{id}/api-keys", wrapper.CreateWebBrokerAPIKey) + m.HandleFunc("DELETE "+options.BaseURL+"/webbroker-apis/{id}/api-keys/{apiKeyName}", wrapper.RevokeWebBrokerAPIKey) + m.HandleFunc("PUT "+options.BaseURL+"/webbroker-apis/{id}/api-keys/{apiKeyName}", wrapper.UpdateWebBrokerAPIKey) + m.HandleFunc("POST "+options.BaseURL+"/webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate", wrapper.RegenerateWebBrokerAPIKey) + m.HandleFunc("GET "+options.BaseURL+"/websub-apis", wrapper.ListWebSubAPIs) + m.HandleFunc("POST "+options.BaseURL+"/websub-apis", wrapper.CreateWebSubAPI) + m.HandleFunc("DELETE "+options.BaseURL+"/websub-apis/{id}", wrapper.DeleteWebSubAPI) + m.HandleFunc("GET "+options.BaseURL+"/websub-apis/{id}", wrapper.GetWebSubAPIById) + m.HandleFunc("PUT "+options.BaseURL+"/websub-apis/{id}", wrapper.UpdateWebSubAPI) + m.HandleFunc("GET "+options.BaseURL+"/websub-apis/{id}/api-keys", wrapper.ListWebSubAPIKeys) + m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/api-keys", wrapper.CreateWebSubAPIKey) + m.HandleFunc("DELETE "+options.BaseURL+"/websub-apis/{id}/api-keys/{apiKeyName}", wrapper.RevokeWebSubAPIKey) + m.HandleFunc("PUT "+options.BaseURL+"/websub-apis/{id}/api-keys/{apiKeyName}", wrapper.UpdateWebSubAPIKey) + m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/api-keys/{apiKeyName}/regenerate", wrapper.RegenerateWebSubAPIKey) + m.HandleFunc("GET "+options.BaseURL+"/websub-apis/{id}/secrets", wrapper.ListWebSubAPISecrets) + m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/secrets", wrapper.CreateWebSubAPISecret) + m.HandleFunc("DELETE "+options.BaseURL+"/websub-apis/{id}/secrets/{secretName}", wrapper.DeleteWebSubAPISecret) + m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/secrets/{secretName}/regenerate", wrapper.RegenerateWebSubAPISecret) + + return m +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "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", + "fP7fAAAA///JK4OH/sQAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/event-gateway/gateway-controller/pkg/config/eventgateway_config.go b/event-gateway/gateway-controller/pkg/config/eventgateway_config.go new file mode 100644 index 0000000000..02b04bd9d7 --- /dev/null +++ b/event-gateway/gateway-controller/pkg/config/eventgateway_config.go @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025, 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 config holds event-gateway-controller-specific configuration, +// moved out of gateway/gateway-controller/pkg/config/config.go. This binary +// loads core config via gateway-controller's own config.Load(...) and then +// additionally loads this EventGatewayConfig section from the same +// config.toml file's "event_gateway" key. +package config + +import ( + "fmt" + "net/url" + "strings" + + toml "github.com/knadh/koanf/parsers/toml/v2" + "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/v2" +) + +// EventGatewayConfig holds event gateway specific configurations. +type EventGatewayConfig struct { + Enabled bool `koanf:"enabled"` + WebSubHubURL string `koanf:"websub_hub_url"` + WebSubHubPort int `koanf:"websub_hub_port"` + RouterHost string `koanf:"router_host"` + WebSubHubListenerPort int `koanf:"websub_hub_listener_port"` + TimeoutSeconds int `koanf:"timeout_seconds"` +} + +// DefaultEventGatewayConfig returns the default configuration (matches the +// defaults core used to apply under Router.EventGateway before the split). +func DefaultEventGatewayConfig() EventGatewayConfig { + return EventGatewayConfig{ + Enabled: false, + WebSubHubURL: "http://host.docker.internal", + WebSubHubPort: 9098, + RouterHost: "localhost", + WebSubHubListenerPort: 8083, + TimeoutSeconds: 30, + } +} + +// Load reads the "router.event_gateway" section from the same config.toml +// file this binary's main.go loads via gateway-controller (core)'s +// config.LoadConfig(...), applying the same defaults core used to apply. +func Load(configPath string) (EventGatewayConfig, error) { + cfg := DefaultEventGatewayConfig() + + k := koanf.New(".") + if err := k.Load(file.Provider(configPath), toml.Parser()); err != nil { + return cfg, fmt.Errorf("failed to load config file: %w", err) + } + if err := k.Unmarshal("router.event_gateway", &cfg); err != nil { + return cfg, fmt.Errorf("failed to unmarshal router.event_gateway: %w", err) + } + return cfg, nil +} + +// Validate validates the event gateway configuration. Only called when Enabled. +func (c *EventGatewayConfig) Validate() error { + if c.WebSubHubPort < 1 || c.WebSubHubPort > 65535 { + return fmt.Errorf("router.event_gateway.websub_hub_port must be between 1 and 65535, got: %d", c.WebSubHubPort) + } + if c.WebSubHubListenerPort < 1 || c.WebSubHubListenerPort > 65535 { + return fmt.Errorf("router.event_gateway.websub_hub_listener_port must be between 1 and 65535, got: %d", c.WebSubHubListenerPort) + } + + // Validate WebSubHubURL if provided - must be a valid http(s) URL + if strings.TrimSpace(c.WebSubHubURL) != "" { + u, err := url.Parse(c.WebSubHubURL) + if err != nil || (u.Scheme != "http" && u.Scheme != "https") { + return fmt.Errorf("router.event_gateway.websub_hub_url must be a valid URL with http or https scheme, got: %s", c.WebSubHubURL) + } + if u.Host == "" { + return fmt.Errorf("router.event_gateway.websub_hub_url must include a valid host, got: %s", c.WebSubHubURL) + } + } + if c.TimeoutSeconds <= 0 { + return fmt.Errorf("router.event_gateway.timeout_seconds must be positive, got: %d", c.TimeoutSeconds) + } + return nil +} diff --git a/event-gateway/gateway-controller/pkg/config/validator.go b/event-gateway/gateway-controller/pkg/config/validator.go new file mode 100644 index 0000000000..52e5456860 --- /dev/null +++ b/event-gateway/gateway-controller/pkg/config/validator.go @@ -0,0 +1,260 @@ +/* + * 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 config + +import ( + "fmt" + "regexp" + "strings" + + api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" + coreconfig "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" +) + +// urlFriendlyNameRegex/versionRegex mirror gateway-controller (core)'s +// config.APIValidator patterns (see pkg/config/api_validator.go). Duplicated +// here since those fields are unexported on APIValidator; the patterns +// themselves are stable, generic string-format rules, not business logic. +var ( + urlFriendlyNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-_\. ]+$`) + versionRegex = regexp.MustCompile(`^v?\d+(\.\d+)?(\.\d+)?$`) +) + +// ValidateWebSubAPI validates a WebSubAPI configuration. Matches the +// utils.KindConfigValidator signature for registration via +// utils.RegisterKindConfigValidator. +func ValidateWebSubAPI(cfg any) (apiName, apiVersion string, validationErrors []coreconfig.ValidationError) { + config, ok := cfg.(eventgateway.WebSubAPI) + if !ok { + return "", "", []coreconfig.ValidationError{{Field: "config", Message: "configuration is not a WebSubAPI"}} + } + + var errors []coreconfig.ValidationError + + if config.Kind != eventgateway.WebSubAPIKindWebSubApi { + errors = append(errors, coreconfig.ValidationError{ + Field: "kind", + Message: "Unsupported kind (must be 'WebSubApi')", + }) + } + + if config.ApiVersion != eventgateway.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1 { + errors = append(errors, coreconfig.ValidationError{ + Field: "version", + Message: "Unsupported API version (must be 'gateway.api-platform.wso2.com/v1')", + }) + } + + errors = append(errors, validateAsyncData(&config.Spec)...) + mgmtMetadata := api.Metadata(config.Metadata) + errors = append(errors, coreconfig.ValidateMetadata(&mgmtMetadata)...) + + return config.Spec.DisplayName, config.Spec.Version, errors +} + +// ValidateWebBrokerAPI validates a WebBrokerApi configuration. Matches the +// utils.KindConfigValidator signature for registration via +// utils.RegisterKindConfigValidator. +func ValidateWebBrokerAPI(cfg any) (apiName, apiVersion string, validationErrors []coreconfig.ValidationError) { + config, ok := cfg.(eventgateway.WebBrokerApi) + if !ok { + return "", "", []coreconfig.ValidationError{{Field: "config", Message: "configuration is not a WebBrokerApi"}} + } + + var errors []coreconfig.ValidationError + + if config.Kind != eventgateway.WebBrokerApiKindWebBrokerApi { + errors = append(errors, coreconfig.ValidationError{ + Field: "kind", + Message: "Unsupported kind (must be 'WebBrokerApi')", + }) + } + + if config.ApiVersion != eventgateway.WebBrokerApiApiVersionGatewayApiPlatformWso2Comv1 { + errors = append(errors, coreconfig.ValidationError{ + Field: "version", + Message: "Unsupported API version (must be 'gateway.api-platform.wso2.com/v1')", + }) + } + + errors = append(errors, validateWebBrokerData(&config.Spec)...) + mgmtMetadata := api.Metadata(config.Metadata) + errors = append(errors, coreconfig.ValidateMetadata(&mgmtMetadata)...) + + return config.Spec.DisplayName, config.Spec.Version, errors +} + +// validateWebBrokerData validates the data section of a WebBrokerApi configuration. +func validateWebBrokerData(spec *eventgateway.WebBrokerApiData) []coreconfig.ValidationError { + var errors []coreconfig.ValidationError + + if spec.DisplayName == "" { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.displayName", + Message: "API name is required", + }) + } else if len(spec.DisplayName) > 100 { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.displayName", + Message: "API name must be 1-100 characters", + }) + } else if !urlFriendlyNameRegex.MatchString(spec.DisplayName) { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.displayName", + Message: "API name must be URL-friendly (only letters, numbers, spaces, hyphens, underscores, and dots allowed)", + }) + } + + if spec.Version == "" { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.version", + Message: "API version is required", + }) + } else if !versionRegex.MatchString(spec.Version) { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.version", + Message: "API version must follow semantic versioning pattern (e.g., v1.0, v2.1.3)", + }) + } + + v := coreconfig.NewAPIValidator() + errors = append(errors, v.ValidateContext(spec.Context)...) + + errors = append(errors, validateWebBrokerChannels(spec.Channels)...) + + return errors +} + +// validateWebBrokerChannels validates the channels map configuration. +func validateWebBrokerChannels(channels map[string]eventgateway.WebBrokerApiChannel) []coreconfig.ValidationError { + var errors []coreconfig.ValidationError + + if len(channels) == 0 { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.channels", + Message: "At least one channel is required", + }) + return errors + } + + for chName := range channels { + if strings.TrimSpace(chName) == "" { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.channels", + Message: "Channel name (key) must not be empty", + }) + continue + } + + if !validatePathParametersForAsyncAPIs(chName) { + errors = append(errors, coreconfig.ValidationError{ + Field: fmt.Sprintf("spec.channels.%s", chName), + Message: "Channel name has {} in parameters", + }) + } + } + + return errors +} + +// validateAsyncData validates the data section of a WebSubAPI configuration. +func validateAsyncData(spec *eventgateway.WebhookAPIData) []coreconfig.ValidationError { + var errors []coreconfig.ValidationError + + if spec.DisplayName == "" { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.displayName", + Message: "API name is required", + }) + } else if len(spec.DisplayName) > 100 { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.displayName", + Message: "API name must be 1-100 characters", + }) + } else if !urlFriendlyNameRegex.MatchString(spec.DisplayName) { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.displayName", + Message: "API name must be URL-friendly (only letters, numbers, spaces, hyphens, underscores, and dots allowed)", + }) + } + + if spec.Version == "" { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.version", + Message: "API version is required", + }) + } else if !versionRegex.MatchString(spec.Version) { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.version", + Message: "API version must follow semantic versioning pattern (e.g., v1.0, v2.1.3)", + }) + } + + v := coreconfig.NewAPIValidator() + errors = append(errors, v.ValidateContext(spec.Context)...) + + var channels map[string]eventgateway.WebSubChannel + if spec.Channels != nil { + channels = *spec.Channels + } + errors = append(errors, validateChannelPolicies(channels)...) + + return errors +} + +// validateChannelPolicies validates the channels map configuration. +func validateChannelPolicies(channelPolicies map[string]eventgateway.WebSubChannel) []coreconfig.ValidationError { + var errors []coreconfig.ValidationError + + if len(channelPolicies) == 0 { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.channels", + Message: "At least one channel is required", + }) + return errors + } + + for chName := range channelPolicies { + if strings.TrimSpace(chName) == "" { + errors = append(errors, coreconfig.ValidationError{ + Field: "spec.channels", + Message: "Channel name (key) must not be empty", + }) + continue + } + + if !validatePathParametersForAsyncAPIs(chName) { + errors = append(errors, coreconfig.ValidationError{ + Field: fmt.Sprintf("spec.channels.%s", chName), + Message: "Channel name has {} in parameters", + }) + } + } + + return errors +} + +// validatePathParametersForAsyncAPIs returns true when the path does not +// contain '{' or '}'. Async/WebSub channel paths do not currently support +// templated path parameters. +func validatePathParametersForAsyncAPIs(path string) bool { + return !strings.Contains(path, "{") && !strings.Contains(path, "}") +} diff --git a/gateway/gateway-controller/pkg/utils/websub_topic_registration_test.go b/event-gateway/gateway-controller/pkg/config/websub_topic_registration_test_staged.go.txt similarity index 100% rename from gateway/gateway-controller/pkg/utils/websub_topic_registration_test.go rename to event-gateway/gateway-controller/pkg/config/websub_topic_registration_test_staged.go.txt diff --git a/gateway/gateway-controller/pkg/eventlistener/webhook_secret_processor.go b/event-gateway/gateway-controller/pkg/eventlistener/webhook_secret_handler.go similarity index 54% rename from gateway/gateway-controller/pkg/eventlistener/webhook_secret_processor.go rename to event-gateway/gateway-controller/pkg/eventlistener/webhook_secret_handler.go index e768bc7db0..83b0356918 100644 --- a/gateway/gateway-controller/pkg/eventlistener/webhook_secret_processor.go +++ b/event-gateway/gateway-controller/pkg/eventlistener/webhook_secret_handler.go @@ -16,6 +16,9 @@ * under the License. */ +// Package eventlistener implements gateway-controller (core)'s +// eventlistener.WebhookSecretEventHandler interface, moved out of core's +// pkg/eventlistener/webhook_secret_processor.go. package eventlistener import ( @@ -25,17 +28,46 @@ import ( "github.com/wso2/api-platform/common/webhooksecret" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/encryption" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" + + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/webhooksecretxds" ) -// processWebhookSecretEvent dispatches webhook secret events by action. -func (l *EventListener) processWebhookSecretEvent(event eventhub.Event) { +// WebhookSecretHandler implements gateway-controller's +// eventlistener.WebhookSecretEventHandler interface. +type WebhookSecretHandler struct { + db storage.Storage + providerManager *encryption.ProviderManager + webhookSecretStore *webhooksecret.WebhookSecretStore + webhookSecretSnapshotManager *webhooksecretxds.SnapshotManager + logger *slog.Logger +} + +// NewWebhookSecretHandler creates a new WebhookSecretHandler. +func NewWebhookSecretHandler( + db storage.Storage, + providerManager *encryption.ProviderManager, + webhookSecretStore *webhooksecret.WebhookSecretStore, + webhookSecretSnapshotManager *webhooksecretxds.SnapshotManager, + logger *slog.Logger, +) *WebhookSecretHandler { + return &WebhookSecretHandler{ + db: db, + providerManager: providerManager, + webhookSecretStore: webhookSecretStore, + webhookSecretSnapshotManager: webhookSecretSnapshotManager, + logger: logger, + } +} + +// HandleEvent dispatches webhook secret events by action. +func (h *WebhookSecretHandler) HandleEvent(event eventhub.Event) { switch event.Action { case "CREATE", "UPDATE": - l.handleWebhookSecretUpsert(event) + h.handleWebhookSecretUpsert(event) case "DELETE": - l.handleWebhookSecretDelete(event) + h.handleWebhookSecretDelete(event) default: - l.logger.Warn("Unknown webhook secret event action", + h.logger.Warn("Unknown webhook secret event action", slog.String("action", event.Action), slog.String("entity_id", event.EntityID)) } @@ -43,44 +75,44 @@ func (l *EventListener) processWebhookSecretEvent(event eventhub.Event) { // handleWebhookSecretUpsert handles webhook secret create/regenerate events. // It fetches the secret from the DB by UUID, decrypts it, and updates the in-memory store. -func (l *EventListener) handleWebhookSecretUpsert(event eventhub.Event) { +func (h *WebhookSecretHandler) handleWebhookSecretUpsert(event eventhub.Event) { artifactUUID, secretUUID, secretName, err := webhooksecret.ParseWebhookSecretEntityID(event.EntityID) if err != nil { - l.logger.Error("Failed to parse webhook secret event entity ID", + h.logger.Error("Failed to parse webhook secret event entity ID", slog.String("action", event.Action), slog.String("entity_id", event.EntityID), slog.Any("error", err)) return } - l.logger.Info("Processing webhook secret upsert event", + h.logger.Info("Processing webhook secret upsert event", slog.String("action", event.Action), slog.String("artifact_uuid", artifactUUID), slog.String("secret_uuid", secretUUID), slog.String("secret_name", secretName), slog.String("event_id", event.EventID)) - if l.webhookSecretStore == nil { - l.logger.Warn("Webhook secret store not available, skipping upsert event", + if h.webhookSecretStore == nil { + h.logger.Warn("Webhook secret store not available, skipping upsert event", slog.String("secret_uuid", secretUUID)) return } - if l.providerManager == nil { - l.logger.Warn("Encryption provider manager not available, skipping webhook secret upsert event", + if h.providerManager == nil { + h.logger.Warn("Encryption provider manager not available, skipping webhook secret upsert event", slog.String("secret_uuid", secretUUID)) return } - ws, err := l.db.GetWebhookSecretByUUID(secretUUID) + ws, err := h.db.GetWebhookSecretByUUID(secretUUID) if err != nil { if storage.IsNotFoundError(err) { - l.logger.Warn("Webhook secret not found in database for upsert event", + h.logger.Warn("Webhook secret not found in database for upsert event", slog.String("secret_uuid", secretUUID), slog.String("event_id", event.EventID)) return } - l.logger.Error("Failed to fetch webhook secret from database", + h.logger.Error("Failed to fetch webhook secret from database", slog.String("secret_uuid", secretUUID), slog.Any("error", err)) return @@ -88,37 +120,37 @@ func (l *EventListener) handleWebhookSecretUpsert(event eventhub.Event) { payload, err := encryption.UnmarshalPayload(string(ws.Ciphertext)) if err != nil { - l.logger.Error("Failed to unmarshal webhook secret ciphertext", + h.logger.Error("Failed to unmarshal webhook secret ciphertext", slog.String("secret_uuid", secretUUID), slog.Any("error", err)) return } - plaintext, err := l.providerManager.Decrypt(payload) + plaintext, err := h.providerManager.Decrypt(payload) if err != nil { - l.logger.Error("Failed to decrypt webhook secret", + h.logger.Error("Failed to decrypt webhook secret", slog.String("secret_uuid", secretUUID), slog.Any("error", err)) return } - if err := l.webhookSecretStore.Store(ws.ArtifactUUID, ws.Name, string(plaintext)); err != nil { - l.logger.Error("Failed to store webhook secret in memory store", + if err := h.webhookSecretStore.Store(ws.ArtifactUUID, ws.Name, string(plaintext)); err != nil { + h.logger.Error("Failed to store webhook secret in memory store", slog.String("secret_uuid", secretUUID), slog.String("artifact_uuid", ws.ArtifactUUID), slog.Any("error", err)) return } - if l.webhookSecretSnapshotManager != nil { - if err := l.webhookSecretSnapshotManager.RefreshSnapshot(); err != nil { - l.logger.Error("Failed to refresh webhook secret xDS snapshot after upsert", + if h.webhookSecretSnapshotManager != nil { + if err := h.webhookSecretSnapshotManager.RefreshSnapshot(); err != nil { + h.logger.Error("Failed to refresh webhook secret xDS snapshot after upsert", slog.String("artifact_uuid", ws.ArtifactUUID), slog.Any("error", err)) } } - l.logger.Info("Successfully processed webhook secret upsert event", + h.logger.Info("Successfully processed webhook secret upsert event", slog.String("action", event.Action), slog.String("artifact_uuid", ws.ArtifactUUID), slog.String("secret_name", ws.Name), @@ -127,44 +159,44 @@ func (l *EventListener) handleWebhookSecretUpsert(event eventhub.Event) { // handleWebhookSecretDelete handles webhook secret delete events. // The entity ID carries artifactUUID, secretUUID, and secretName to avoid a DB round-trip. -func (l *EventListener) handleWebhookSecretDelete(event eventhub.Event) { +func (h *WebhookSecretHandler) handleWebhookSecretDelete(event eventhub.Event) { artifactUUID, secretUUID, secretName, err := webhooksecret.ParseWebhookSecretEntityID(event.EntityID) if err != nil { - l.logger.Error("Failed to parse webhook secret delete event entity ID", + h.logger.Error("Failed to parse webhook secret delete event entity ID", slog.String("entity_id", event.EntityID), slog.Any("error", err)) return } - l.logger.Info("Processing webhook secret delete event", + h.logger.Info("Processing webhook secret delete event", slog.String("artifact_uuid", artifactUUID), slog.String("secret_uuid", secretUUID), slog.String("secret_name", secretName), slog.String("event_id", event.EventID)) - if l.webhookSecretStore == nil { - l.logger.Warn("Webhook secret store not available, skipping delete event", + if h.webhookSecretStore == nil { + h.logger.Warn("Webhook secret store not available, skipping delete event", slog.String("secret_uuid", secretUUID)) return } - if err := l.webhookSecretStore.Remove(artifactUUID, secretName); err != nil && err != webhooksecret.ErrNotFound { - l.logger.Error("Failed to remove webhook secret from memory store", + if err := h.webhookSecretStore.Remove(artifactUUID, secretName); err != nil && err != webhooksecret.ErrNotFound { + h.logger.Error("Failed to remove webhook secret from memory store", slog.String("artifact_uuid", artifactUUID), slog.String("secret_name", secretName), slog.Any("error", err)) return } - if l.webhookSecretSnapshotManager != nil { - if err := l.webhookSecretSnapshotManager.RefreshSnapshot(); err != nil { - l.logger.Error("Failed to refresh webhook secret xDS snapshot after delete", + if h.webhookSecretSnapshotManager != nil { + if err := h.webhookSecretSnapshotManager.RefreshSnapshot(); err != nil { + h.logger.Error("Failed to refresh webhook secret xDS snapshot after delete", slog.String("artifact_uuid", artifactUUID), slog.Any("error", err)) } } - l.logger.Info("Successfully processed webhook secret delete event", + h.logger.Info("Successfully processed webhook secret delete event", slog.String("artifact_uuid", artifactUUID), slog.String("secret_name", secretName), slog.String("event_id", event.EventID)) diff --git a/event-gateway/gateway-controller/pkg/handler/helpers.go b/event-gateway/gateway-controller/pkg/handler/helpers.go new file mode 100644 index 0000000000..d7fe4d657d --- /dev/null +++ b/event-gateway/gateway-controller/pkg/handler/helpers.go @@ -0,0 +1,65 @@ +/* + * 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 handler + +import ( + "errors" + "fmt" + "net/http" + + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/metrics" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/templateengine" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/templateengine/funcs" + "github.com/wso2/go-httpkit/httputil" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" +) + +// mapRenderError checks whether err wraps a *templateengine.RenderError and, if so, +// writes a 400 response with a user-friendly message and returns true. +// operation is used for metrics labelling (e.g. "create", "update"). +// Returns false when err is not a RenderError, allowing the caller to proceed. +func mapRenderError(w http.ResponseWriter, operation string, err error) bool { + var renderErr *templateengine.RenderError + if !errors.As(err, &renderErr) { + return false + } + metrics.ValidationErrorsTotal.WithLabelValues(operation, "render_failed").Inc() + var secretErr *funcs.SecretError + if errors.As(renderErr, &secretErr) { + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ + Status: "error", + Message: secretErr.Error(), + }) + return true + } + var tmplParseErr *templateengine.TemplateParseError + if errors.As(renderErr, &tmplParseErr) { + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ + Status: "error", + Message: tmplParseErr.Error(), + }) + return true + } + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ + Status: "error", + Message: fmt.Sprintf("Failed to render configuration: %v", renderErr.Cause), + }) + return true +} diff --git a/event-gateway/gateway-controller/pkg/handler/resource_response.go b/event-gateway/gateway-controller/pkg/handler/resource_response.go new file mode 100644 index 0000000000..14138c75f3 --- /dev/null +++ b/event-gateway/gateway-controller/pkg/handler/resource_response.go @@ -0,0 +1,82 @@ +/* + * 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 handler + +import ( + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" +) + +// buildResourceStatus builds the server-managed status block for a stored config. +func buildResourceStatus(cfg *models.StoredConfig) eventgateway.ResourceStatus { + id := cfg.Handle + state := eventgateway.ResourceStatusState(cfg.DesiredState) + createdAt := cfg.CreatedAt + updatedAt := cfg.UpdatedAt + + status := eventgateway.ResourceStatus{ + Id: &id, + State: &state, + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + } + if cfg.DeployedAt != nil { + deployedAt := *cfg.DeployedAt + status.DeployedAt = &deployedAt + } + return status +} + +// buildResourceResponse merges a WebSubAPI/WebBrokerApi configuration value +// with the server-managed status block. Any user-provided Status in the input +// is replaced with the authoritative server value. Unknown types are returned +// unchanged so callers can fall through to a generic JSON response if required. +func buildResourceResponse(cfg any, status eventgateway.ResourceStatus) any { + switch v := cfg.(type) { + case eventgateway.WebSubAPI: + v.Status = &status + return v + case *eventgateway.WebSubAPI: + if v == nil { + return nil + } + cp := *v + cp.Status = &status + return cp + case eventgateway.WebBrokerApi: + v.Status = &status + return v + case *eventgateway.WebBrokerApi: + if v == nil { + return nil + } + cp := *v + cp.Status = &status + return cp + default: + return cfg + } +} + +// buildResourceResponseFromStored is a convenience wrapper combining +// buildResourceStatus and buildResourceResponse. +func buildResourceResponseFromStored(cfg any, stored *models.StoredConfig) any { + return buildResourceResponse(cfg, buildResourceStatus(stored)) +} diff --git a/event-gateway/gateway-controller/pkg/handler/server.go b/event-gateway/gateway-controller/pkg/handler/server.go new file mode 100644 index 0000000000..aa42b96fd6 --- /dev/null +++ b/event-gateway/gateway-controller/pkg/handler/server.go @@ -0,0 +1,177 @@ +/* + * 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 handler implements the eventgateway.ServerInterface (WebSub/WebBroker +// management API), built against gateway-controller (core) as a library. +package handler + +import ( + "log/slog" + "net/http" + "time" + + "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/handlers/handlerkit" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/controlplane" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" + + commonmodels "github.com/wso2/api-platform/common/models" + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" + eventgatewayconfig "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/webhooksecretservice" +) + +// toManagementAPIKeyCreationRequest converts the eventgateway-local +// APIKeyCreationRequest (bound from the request body against this module's +// own generated spec) into core's management.APIKeyCreationRequest, which +// utils.APIKeyCreationParams/APIKeyUpdateParams require. The two types are +// generated from identical schema definitions (core's spec duplicates the +// shared api-key schemas into this module's spec — see +// api/eventgateway-openapi.yaml) but a plain Go struct conversion isn't legal +// here since the nested ExpiresIn.Unit field uses a package-local named type +// on each side, so each field is copied explicitly instead. +func toManagementAPIKeyCreationRequest(r eventgateway.APIKeyCreationRequest) api.APIKeyCreationRequest { + out := api.APIKeyCreationRequest{ + ApiKey: r.ApiKey, + ExpiresAt: r.ExpiresAt, + ExternalRefId: r.ExternalRefId, + Issuer: r.Issuer, + MaskedApiKey: r.MaskedApiKey, + Name: r.Name, + } + if r.ExpiresIn != nil { + out.ExpiresIn = &struct { + Duration int `json:"duration" yaml:"duration"` + Unit api.APIKeyCreationRequestExpiresInUnit `json:"unit" yaml:"unit"` + }{ + Duration: r.ExpiresIn.Duration, + Unit: api.APIKeyCreationRequestExpiresInUnit(r.ExpiresIn.Unit), + } + } + return out +} + +// toManagementAPIKeyRegenerationRequest converts the eventgateway-local +// APIKeyRegenerationRequest into core's management.APIKeyRegenerationRequest. +// See toManagementAPIKeyCreationRequest for why field-by-field copying is +// used instead of a direct struct conversion. +func toManagementAPIKeyRegenerationRequest(r eventgateway.APIKeyRegenerationRequest) api.APIKeyRegenerationRequest { + out := api.APIKeyRegenerationRequest{ + ExpiresAt: r.ExpiresAt, + } + if r.ExpiresIn != nil { + out.ExpiresIn = &struct { + Duration int `json:"duration" yaml:"duration"` + Unit api.APIKeyRegenerationRequestExpiresInUnit `json:"unit" yaml:"unit"` + }{ + Duration: r.ExpiresIn.Duration, + Unit: api.APIKeyRegenerationRequestExpiresInUnit(r.ExpiresIn.Unit), + } + } + return out +} + +// DeploymentSearcher is satisfied by core's *handlers.APIServer. WebSub/WebBroker +// list endpoints delegate filtered search queries to it rather than +// duplicating the generic search-by-kind implementation. +type DeploymentSearcher interface { + SearchDeployments(w http.ResponseWriter, r *http.Request, kind string) +} + +// WebSubServer implements eventgateway.ServerInterface (WebSub and WebBroker +// management endpoints). +type WebSubServer struct { + store *storage.ConfigStore + db storage.Storage + deploymentService *utils.APIDeploymentService + apiKeyService *utils.APIKeyService + controlPlaneClient controlplane.ControlPlaneClient + systemConfig *config.Config + eventGatewayConfig eventgatewayconfig.EventGatewayConfig + httpClient *http.Client + logger *slog.Logger + webhookSecretService *webhooksecretservice.WebhookSecretService + deploymentSearcher DeploymentSearcher + + eventHub eventhub.EventHub + gatewayID string +} + +// Deps bundles the dependencies WebSubServer needs, sourced from gateway-controller +// (core) constructors plus this module's own webhook-secret and config packages. +type Deps struct { + Store *storage.ConfigStore + DB storage.Storage + DeploymentService *utils.APIDeploymentService + APIKeyService *utils.APIKeyService + ControlPlaneClient controlplane.ControlPlaneClient + SystemConfig *config.Config + EventGatewayConfig eventgatewayconfig.EventGatewayConfig + HTTPClient *http.Client + Logger *slog.Logger + WebhookSecretService *webhooksecretservice.WebhookSecretService + DeploymentSearcher DeploymentSearcher + EventHub eventhub.EventHub + GatewayID string +} + +// NewWebSubServer creates a new WebSubServer. +func NewWebSubServer(d Deps) *WebSubServer { + return &WebSubServer{ + store: d.Store, + db: d.DB, + deploymentService: d.DeploymentService, + apiKeyService: d.APIKeyService, + controlPlaneClient: d.ControlPlaneClient, + systemConfig: d.SystemConfig, + eventGatewayConfig: d.EventGatewayConfig, + httpClient: d.HTTPClient, + logger: d.Logger, + webhookSecretService: d.WebhookSecretService, + deploymentSearcher: d.DeploymentSearcher, + eventHub: d.EventHub, + gatewayID: d.GatewayID, + } +} + +func (s *WebSubServer) deploymentPusher() *handlerkit.DeploymentPusher { + return &handlerkit.DeploymentPusher{ + Store: s.store, + ControlPlaneClient: s.controlPlaneClient, + SystemConfig: s.systemConfig, + } +} + +func (s *WebSubServer) waitForDeploymentAndPush(configID string, correlationID string, minDeployedAt *time.Time, log *slog.Logger) { + s.deploymentPusher().WaitForDeploymentAndPush(configID, correlationID, minDeployedAt, log) +} + +func (s *WebSubServer) publishWebSubEvent(eventType eventhub.EventType, action, entityID, correlationID string, logger *slog.Logger) { + (&handlerkit.EventPublisher{EventHub: s.eventHub, GatewayID: s.gatewayID}).PublishEvent(eventType, action, entityID, correlationID, logger) +} + +func (s *WebSubServer) bindRequestBody(r *http.Request, request interface{}) error { + return handlerkit.BindRequestBody(r, request) +} + +func (s *WebSubServer) extractAuthenticatedUser(w http.ResponseWriter, r *http.Request, operationName string, correlationID string) (*commonmodels.AuthContext, bool) { + return handlerkit.ExtractAuthenticatedUser(w, r, s.logger, operationName, correlationID) +} diff --git a/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go b/event-gateway/gateway-controller/pkg/handler/webbroker_api_handler.go similarity index 69% rename from gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go rename to event-gateway/gateway-controller/pkg/handler/webbroker_api_handler.go index 3e521dd24d..7debf1f7ae 100644 --- a/gateway/gateway-controller/pkg/api/handlers/webbroker_api_handler.go +++ b/event-gateway/gateway-controller/pkg/handler/webbroker_api_handler.go @@ -16,7 +16,7 @@ * under the License. */ -package handlers +package handler import ( "fmt" @@ -26,22 +26,23 @@ import ( "strings" "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" "github.com/wso2/go-httpkit/httputil" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" ) // CreateWebBrokerApi handles POST /webbroker-apis -func (s *APIServer) CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) { +func (s *WebSubServer) CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) { log := middleware.GetLogger(r, s.logger) body, err := io.ReadAll(r.Body) if err != nil { log.Error("Failed to read request body", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ Status: "error", Message: "Failed to read request body", }) @@ -62,7 +63,7 @@ func (s *APIServer) CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) { if err != nil { log.Error("Failed to deploy WebBrokerApi configuration", slog.Any("error", err)) if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{ Status: "error", Message: err.Error(), }) @@ -71,7 +72,7 @@ func (s *APIServer) CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) { if mapRenderError(w, "create", err) { return } - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ Status: "error", Message: err.Error(), }) @@ -92,11 +93,11 @@ func (s *APIServer) CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) { } // ListWebBrokerApis handles GET /webbroker-apis -func (s *APIServer) ListWebBrokerApis(w http.ResponseWriter, r *http.Request, params api.ListWebBrokerApisParams) { +func (s *WebSubServer) ListWebBrokerApis(w http.ResponseWriter, r *http.Request, params eventgateway.ListWebBrokerApisParams) { configs, err := s.db.GetAllConfigsByKind(string(models.KindWebBrokerApi)) if err != nil { s.logger.Error("Failed to list WebBrokerApis", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{ Status: "error", Message: "Failed to list WebBrokerApi configurations", }) @@ -118,7 +119,7 @@ func (s *APIServer) ListWebBrokerApis(w http.ResponseWriter, r *http.Request, pa } // GetWebBrokerApiById handles GET /webbroker-apis/{id} -func (s *APIServer) GetWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) GetWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id @@ -126,14 +127,14 @@ func (s *APIServer) GetWebBrokerApiById(w http.ResponseWriter, r *http.Request, if err != nil { if storage.IsDatabaseUnavailableError(err) { log.Error("Database unavailable", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusServiceUnavailable, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusServiceUnavailable, eventgateway.ErrorResponse{ Status: "error", Message: "Database is temporarily unavailable", }) return } log.Warn("WebBrokerApi not found", slog.String("handle", handle)) - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{ Status: "error", Message: "WebBrokerApi not found", }) @@ -144,7 +145,7 @@ func (s *APIServer) GetWebBrokerApiById(w http.ResponseWriter, r *http.Request, } // DeleteWebBrokerApiById handles DELETE /webbroker-apis/{id} -func (s *APIServer) DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id @@ -152,14 +153,14 @@ func (s *APIServer) DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Reques if err != nil { if storage.IsDatabaseUnavailableError(err) { log.Error("Database unavailable", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusServiceUnavailable, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusServiceUnavailable, eventgateway.ErrorResponse{ Status: "error", Message: "Database is temporarily unavailable", }) return } log.Warn("WebBrokerApi not found", slog.String("handle", handle)) - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{ Status: "error", Message: "WebBrokerApi not found", }) @@ -170,7 +171,7 @@ func (s *APIServer) DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Reques if err := s.db.DeleteConfig(cfg.UUID); err != nil { log.Error("Failed to delete WebBrokerApi from database", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{ Status: "error", Message: "Failed to delete configuration", }) @@ -192,7 +193,7 @@ func (s *APIServer) DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Reques // CreateWebBrokerAPIKey implements ServerInterface.CreateWebBrokerAPIKey // (POST /webbroker-apis/{id}/api-keys) -func (s *APIServer) CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -202,20 +203,20 @@ func (s *APIServer) CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request return } - var request api.APIKeyCreationRequest + var request eventgateway.APIKeyCreationRequest if err := s.bindRequestBody(r, &request); err != nil { log.Error("Failed to parse request body for WebBroker API key creation", slog.Any("error", err), slog.String("handle", handle), slog.String("correlation_id", correlationID)) - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) return } params := utils.APIKeyCreationParams{ Kind: models.KindWebBrokerApi, Handle: handle, - Request: request, + Request: toManagementAPIKeyCreationRequest(request), User: user, CorrelationID: correlationID, Logger: log, @@ -224,12 +225,12 @@ func (s *APIServer) CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request result, err := s.apiKeyService.CreateAPIKey(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API '%s' not found", handle)}) } else if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) } else { log.Error("Failed to create WebBroker API key", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to create API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to create API key"}) } return } @@ -239,7 +240,7 @@ func (s *APIServer) CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request // ListWebBrokerAPIKeys implements ServerInterface.ListWebBrokerAPIKeys // (GET /webbroker-apis/{id}/api-keys) -func (s *APIServer) ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -260,10 +261,10 @@ func (s *APIServer) ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request, result, err := s.apiKeyService.ListAPIKeys(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API '%s' not found", handle)}) } else { log.Error("Failed to list WebBroker API keys", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to list API keys"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to list API keys"}) } return } @@ -273,7 +274,7 @@ func (s *APIServer) ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request, // RevokeWebBrokerAPIKey implements ServerInterface.RevokeWebBrokerAPIKey // (DELETE /webbroker-apis/{id}/api-keys/{apiKeyName}) -func (s *APIServer) RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { +func (s *WebSubServer) RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -295,10 +296,10 @@ func (s *APIServer) RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request result, err := s.apiKeyService.RevokeAPIKey(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API '%s' not found", handle)}) } else { log.Error("Failed to revoke WebBroker API key", slog.String("handle", handle), slog.String("key", apiKeyName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to revoke API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to revoke API key"}) } return } @@ -308,7 +309,7 @@ func (s *APIServer) RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request // UpdateWebBrokerAPIKey implements ServerInterface.UpdateWebBrokerAPIKey // (PUT /webbroker-apis/{id}/api-keys/{apiKeyName}) -func (s *APIServer) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { +func (s *WebSubServer) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -318,14 +319,14 @@ func (s *APIServer) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request return } - var request api.APIKeyCreationRequest + var request eventgateway.APIKeyCreationRequest if err := s.bindRequestBody(r, &request); err != nil { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) return } if request.ApiKey == nil || strings.TrimSpace(*request.ApiKey) == "" { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: "apiKey is required"}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: "apiKey is required"}) return } @@ -333,7 +334,7 @@ func (s *APIServer) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request Kind: models.KindWebBrokerApi, Handle: handle, APIKeyName: apiKeyName, - Request: request, + Request: toManagementAPIKeyCreationRequest(request), User: user, CorrelationID: correlationID, Logger: log, @@ -342,14 +343,14 @@ func (s *APIServer) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request result, err := s.apiKeyService.UpdateAPIKey(params) if err != nil { if storage.IsOperationNotAllowedError(err) { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) } else if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API or API key '%s' not found", apiKeyName)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API or API key '%s' not found", apiKeyName)}) } else if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) } else { log.Error("Failed to update WebBroker API key", slog.String("handle", handle), slog.String("key", apiKeyName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to update API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to update API key"}) } return } @@ -359,7 +360,7 @@ func (s *APIServer) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request // RegenerateWebBrokerAPIKey implements ServerInterface.RegenerateWebBrokerAPIKey // (POST /webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate) -func (s *APIServer) RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { +func (s *WebSubServer) RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -369,9 +370,9 @@ func (s *APIServer) RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Req return } - var request api.APIKeyRegenerationRequest + var request eventgateway.APIKeyRegenerationRequest if err := s.bindRequestBody(r, &request); err != nil { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) return } @@ -379,7 +380,7 @@ func (s *APIServer) RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Req Kind: models.KindWebBrokerApi, Handle: handle, APIKeyName: apiKeyName, - Request: request, + Request: toManagementAPIKeyRegenerationRequest(request), User: user, CorrelationID: correlationID, Logger: log, @@ -388,10 +389,10 @@ func (s *APIServer) RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Req result, err := s.apiKeyService.RegenerateAPIKey(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API or API key '%s' not found", apiKeyName)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebBroker API or API key '%s' not found", apiKeyName)}) } else { log.Error("Failed to regenerate WebBroker API key", slog.String("handle", handle), slog.String("key", apiKeyName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to regenerate API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to regenerate API key"}) } return } diff --git a/gateway/gateway-controller/pkg/api/handlers/websub_api_handler.go b/event-gateway/gateway-controller/pkg/handler/websub_api_handler.go similarity index 63% rename from gateway/gateway-controller/pkg/api/handlers/websub_api_handler.go rename to event-gateway/gateway-controller/pkg/handler/websub_api_handler.go index 6526ae7e1b..18e931fb1b 100644 --- a/gateway/gateway-controller/pkg/api/handlers/websub_api_handler.go +++ b/event-gateway/gateway-controller/pkg/handler/websub_api_handler.go @@ -16,7 +16,7 @@ * under the License. */ -package handlers +package handler import ( "context" @@ -28,23 +28,24 @@ import ( "time" "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" "github.com/wso2/go-httpkit/httputil" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" ) // CreateWebSubAPI implements ServerInterface.CreateWebSubAPI // (POST /websub-apis) -func (s *APIServer) CreateWebSubAPI(w http.ResponseWriter, r *http.Request) { +func (s *WebSubServer) CreateWebSubAPI(w http.ResponseWriter, r *http.Request) { log := middleware.GetLogger(r, s.logger) body, err := io.ReadAll(r.Body) if err != nil { log.Error("Failed to read request body", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ Status: "error", Message: "Failed to read request body", }) @@ -65,7 +66,7 @@ func (s *APIServer) CreateWebSubAPI(w http.ResponseWriter, r *http.Request) { if err != nil { log.Error("Failed to deploy WebSub API configuration", slog.Any("error", err)) if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{ Status: "error", Message: err.Error(), }) @@ -74,7 +75,7 @@ func (s *APIServer) CreateWebSubAPI(w http.ResponseWriter, r *http.Request) { if mapRenderError(w, "create", err) { return } - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ Status: "error", Message: err.Error(), }) @@ -96,19 +97,19 @@ func (s *APIServer) CreateWebSubAPI(w http.ResponseWriter, r *http.Request) { // ListWebSubAPIs implements ServerInterface.ListWebSubAPIs // (GET /websub-apis) -func (s *APIServer) ListWebSubAPIs(w http.ResponseWriter, r *http.Request, params api.ListWebSubAPIsParams) { +func (s *WebSubServer) ListWebSubAPIs(w http.ResponseWriter, r *http.Request, params eventgateway.ListWebSubAPIsParams) { if (params.DisplayName != nil && *params.DisplayName != "") || (params.Version != nil && *params.Version != "") || (params.Context != nil && *params.Context != "") || (params.Status != nil && *params.Status != "") { - s.SearchDeployments(w, r, string(api.WebSubAPIKindWebSubApi)) + s.deploymentSearcher.SearchDeployments(w, r, "WebSubApi") return } - configs, err := s.db.GetAllConfigsByKind(string(api.WebSubAPIKindWebSubApi)) + configs, err := s.db.GetAllConfigsByKind("WebSubApi") if err != nil { s.logger.Error("Failed to list WebSub APIs", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{ Status: "error", Message: "Failed to list WebSub API configurations", }) @@ -141,14 +142,14 @@ func (s *APIServer) ListWebSubAPIs(w http.ResponseWriter, r *http.Request, param // GetWebSubAPIById implements ServerInterface.GetWebSubAPIById // (GET /websub-apis/{id}) -func (s *APIServer) GetWebSubAPIById(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) GetWebSubAPIById(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id cfg, err := s.db.GetConfigByKindAndHandle(models.KindWebSubApi, handle) if err != nil { if storage.IsDatabaseUnavailableError(err) { - httputil.WriteJSON(w, http.StatusServiceUnavailable, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusServiceUnavailable, eventgateway.ErrorResponse{ Status: "error", Message: "Database storage not available", }) @@ -156,7 +157,7 @@ func (s *APIServer) GetWebSubAPIById(w http.ResponseWriter, r *http.Request, id } log.Warn("WebSub API configuration not found", slog.String("handle", handle)) - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{ Status: "error", Message: fmt.Sprintf("WebSub API configuration with handle '%s' not found", handle), }) @@ -168,14 +169,14 @@ func (s *APIServer) GetWebSubAPIById(w http.ResponseWriter, r *http.Request, id // UpdateWebSubAPI implements ServerInterface.UpdateWebSubAPI // (PUT /websub-apis/{id}) -func (s *APIServer) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id body, err := io.ReadAll(r.Body) if err != nil { log.Error("Failed to read request body", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ Status: "error", Message: "Failed to read request body", }) @@ -186,7 +187,7 @@ func (s *APIServer) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id s if err != nil { log.Warn("WebSub API configuration not found", slog.String("handle", handle)) - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{ Status: "error", Message: fmt.Sprintf("WebSub API configuration with handle '%s' not found", handle), }) @@ -207,7 +208,7 @@ func (s *APIServer) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id s if err != nil { log.Error("Failed to update WebSub API configuration", slog.Any("error", err)) if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{ Status: "error", Message: err.Error(), }) @@ -216,7 +217,7 @@ func (s *APIServer) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id s if mapRenderError(w, "update", err) { return } - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{ Status: "error", Message: err.Error(), }) @@ -234,7 +235,7 @@ func (s *APIServer) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id s // DeleteWebSubAPI implements ServerInterface.DeleteWebSubAPI // (DELETE /websub-apis/{id}) -func (s *APIServer) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -243,7 +244,7 @@ func (s *APIServer) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id s if err != nil { log.Warn("WebSub API configuration not found", slog.String("handle", handle)) - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{ Status: "error", Message: fmt.Sprintf("WebSub API configuration with handle '%s' not found", handle), }) @@ -252,7 +253,7 @@ func (s *APIServer) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id s if err := s.db.DeleteConfig(cfg.UUID); err != nil { log.Error("Failed to delete WebSub API config from database", slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{ + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{ Status: "error", Message: "Failed to delete configuration", }) @@ -269,9 +270,9 @@ func (s *APIServer) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id s topicsToUnregister := s.deploymentService.GetTopicsForDelete(*cfg) for _, topic := range topicsToUnregister { ctx, cancel := context.WithTimeout(context.Background(), - time.Duration(s.routerConfig.EventGateway.TimeoutSeconds)*time.Second) + time.Duration(s.eventGatewayConfig.TimeoutSeconds)*time.Second) if err := s.deploymentService.UnregisterTopicWithHub(ctx, s.httpClient, topic, - s.routerConfig.EventGateway.RouterHost, s.routerConfig.EventGateway.WebSubHubListenerPort, log); err != nil { + s.eventGatewayConfig.RouterHost, s.eventGatewayConfig.WebSubHubListenerPort, log); err != nil { log.Error("Failed to deregister topic from WebSubHub", slog.Any("error", err), slog.String("topic", topic)) @@ -297,7 +298,7 @@ func (s *APIServer) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id s // CreateWebSubAPIKey implements ServerInterface.CreateWebSubAPIKey // (POST /websub-apis/{id}/api-keys) -func (s *APIServer) CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -307,20 +308,20 @@ func (s *APIServer) CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request, i return } - var request api.APIKeyCreationRequest + var request eventgateway.APIKeyCreationRequest if err := s.bindRequestBody(r, &request); err != nil { log.Error("Failed to parse request body for WebSub API key creation", slog.Any("error", err), slog.String("handle", handle), slog.String("correlation_id", correlationID)) - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) return } params := utils.APIKeyCreationParams{ Kind: models.KindWebSubApi, Handle: handle, - Request: request, + Request: toManagementAPIKeyCreationRequest(request), User: user, CorrelationID: correlationID, Logger: log, @@ -329,12 +330,12 @@ func (s *APIServer) CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request, i result, err := s.apiKeyService.CreateAPIKey(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) } else if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) } else { log.Error("Failed to create WebSub API key", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to create API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to create API key"}) } return } @@ -344,7 +345,7 @@ func (s *APIServer) CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request, i // ListWebSubAPIKeys implements ServerInterface.ListWebSubAPIKeys // (GET /websub-apis/{id}/api-keys) -func (s *APIServer) ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request, id string) { +func (s *WebSubServer) ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request, id string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -365,10 +366,10 @@ func (s *APIServer) ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request, id result, err := s.apiKeyService.ListAPIKeys(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) } else { log.Error("Failed to list WebSub API keys", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to list API keys"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to list API keys"}) } return } @@ -378,7 +379,7 @@ func (s *APIServer) ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request, id // RevokeWebSubAPIKey implements ServerInterface.RevokeWebSubAPIKey // (DELETE /websub-apis/{id}/api-keys/{apiKeyName}) -func (s *APIServer) RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { +func (s *WebSubServer) RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -400,10 +401,10 @@ func (s *APIServer) RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request, i result, err := s.apiKeyService.RevokeAPIKey(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) } else { log.Error("Failed to revoke WebSub API key", slog.String("handle", handle), slog.String("key", apiKeyName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to revoke API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to revoke API key"}) } return } @@ -413,7 +414,7 @@ func (s *APIServer) RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request, i // UpdateWebSubAPIKey implements ServerInterface.UpdateWebSubAPIKey // (PUT /websub-apis/{id}/api-keys/{apiKeyName}) -func (s *APIServer) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { +func (s *WebSubServer) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -423,14 +424,14 @@ func (s *APIServer) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, i return } - var request api.APIKeyCreationRequest + var request eventgateway.APIKeyCreationRequest if err := s.bindRequestBody(r, &request); err != nil { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) return } if request.ApiKey == nil || strings.TrimSpace(*request.ApiKey) == "" { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: "apiKey is required"}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: "apiKey is required"}) return } @@ -438,7 +439,7 @@ func (s *APIServer) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, i Kind: models.KindWebSubApi, Handle: handle, APIKeyName: apiKeyName, - Request: request, + Request: toManagementAPIKeyCreationRequest(request), User: user, CorrelationID: correlationID, Logger: log, @@ -447,14 +448,14 @@ func (s *APIServer) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, i result, err := s.apiKeyService.UpdateAPIKey(params) if err != nil { if storage.IsOperationNotAllowedError(err) { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) } else if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API or API key '%s' not found", apiKeyName)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API or API key '%s' not found", apiKeyName)}) } else if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) } else { log.Error("Failed to update WebSub API key", slog.String("handle", handle), slog.String("key", apiKeyName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to update API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to update API key"}) } return } @@ -464,7 +465,7 @@ func (s *APIServer) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, i // RegenerateWebSubAPIKey implements ServerInterface.RegenerateWebSubAPIKey // (POST /websub-apis/{id}/api-keys/{apiKeyName}/regenerate) -func (s *APIServer) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { +func (s *WebSubServer) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) { log := middleware.GetLogger(r, s.logger) handle := id correlationID := middleware.GetCorrelationID(r) @@ -474,9 +475,9 @@ func (s *APIServer) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Reques return } - var request api.APIKeyRegenerationRequest + var request eventgateway.APIKeyRegenerationRequest if err := s.bindRequestBody(r, &request); err != nil { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %v", err)}) return } @@ -484,7 +485,7 @@ func (s *APIServer) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Reques Kind: models.KindWebSubApi, Handle: handle, APIKeyName: apiKeyName, - Request: request, + Request: toManagementAPIKeyRegenerationRequest(request), User: user, CorrelationID: correlationID, Logger: log, @@ -493,10 +494,10 @@ func (s *APIServer) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Reques result, err := s.apiKeyService.RegenerateAPIKey(params) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API or API key '%s' not found", apiKeyName)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API or API key '%s' not found", apiKeyName)}) } else { log.Error("Failed to regenerate WebSub API key", slog.String("handle", handle), slog.String("key", apiKeyName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to regenerate API key"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to regenerate API key"}) } return } @@ -510,56 +511,56 @@ func (s *APIServer) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Reques // CreateWebSubAPISecret implements ServerInterface.CreateWebSubAPISecret // (POST /websub-apis/{id}/secrets) -func (s *APIServer) CreateWebSubAPISecret(w http.ResponseWriter, r *http.Request, handle string) { +func (s *WebSubServer) CreateWebSubAPISecret(w http.ResponseWriter, r *http.Request, handle string) { log := middleware.GetLogger(r, s.logger) correlationID := middleware.GetCorrelationID(r) cfg, err := s.db.GetConfigByKindAndHandle(models.KindWebSubApi, handle) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } if storage.IsDatabaseUnavailableError(err) { - httputil.WriteJSON(w, http.StatusServiceUnavailable, api.ErrorResponse{Status: "error", Message: "Storage unavailable"}) + httputil.WriteJSON(w, http.StatusServiceUnavailable, eventgateway.ErrorResponse{Status: "error", Message: "Storage unavailable"}) return } log.Error("Failed to look up WebSub API", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) return } if cfg == nil { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } - var request api.WebhookSecretCreationRequest + var request eventgateway.WebhookSecretCreationRequest if err := s.bindRequestBody(r, &request); err != nil { - httputil.WriteJSON(w, http.StatusBadRequest, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %s", err.Error())}) + httputil.WriteJSON(w, http.StatusBadRequest, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Invalid request body: %s", err.Error())}) return } ws, plaintext, err := s.webhookSecretService.Generate(cfg.UUID, request.DisplayName, correlationID) if err != nil { if storage.IsConflictError(err) { - httputil.WriteJSON(w, http.StatusConflict, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusConflict, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) return } if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: err.Error()}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: err.Error()}) return } log.Error("Failed to generate webhook secret", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to generate webhook secret"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to generate webhook secret"}) return } - secretStatus := api.WebhookSecretInfoStatus(ws.Status) - httputil.WriteJSON(w, http.StatusCreated, api.WebhookSecretCreationResponse{ + secretStatus := eventgateway.WebhookSecretInfoStatus(ws.Status) + httputil.WriteJSON(w, http.StatusCreated, eventgateway.WebhookSecretCreationResponse{ Status: "success", Message: "Webhook secret generated successfully", Secret: plaintext, - WebhookSecret: &api.WebhookSecretInfo{ + WebhookSecret: &eventgateway.WebhookSecretInfo{ Name: &ws.Name, DisplayName: &ws.DisplayName, Status: &secretStatus, @@ -571,39 +572,39 @@ func (s *APIServer) CreateWebSubAPISecret(w http.ResponseWriter, r *http.Request // ListWebSubAPISecrets implements ServerInterface.ListWebSubAPISecrets // (GET /websub-apis/{id}/secrets) -func (s *APIServer) ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request, handle string) { +func (s *WebSubServer) ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request, handle string) { log := middleware.GetLogger(r, s.logger) cfg, err := s.db.GetConfigByKindAndHandle(models.KindWebSubApi, handle) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } if storage.IsDatabaseUnavailableError(err) { - httputil.WriteJSON(w, http.StatusServiceUnavailable, api.ErrorResponse{Status: "error", Message: "Storage unavailable"}) + httputil.WriteJSON(w, http.StatusServiceUnavailable, eventgateway.ErrorResponse{Status: "error", Message: "Storage unavailable"}) return } log.Error("Failed to look up WebSub API", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) return } if cfg == nil { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } wsList, err := s.webhookSecretService.List(cfg.UUID) if err != nil { log.Error("Failed to list webhook secrets", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to list webhook secrets"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to list webhook secrets"}) return } - items := make([]api.WebhookSecretInfo, 0, len(wsList)) + items := make([]eventgateway.WebhookSecretInfo, 0, len(wsList)) for _, ws := range wsList { - secretStatus := api.WebhookSecretInfoStatus(ws.Status) - items = append(items, api.WebhookSecretInfo{ + secretStatus := eventgateway.WebhookSecretInfoStatus(ws.Status) + items = append(items, eventgateway.WebhookSecretInfo{ Name: &ws.Name, DisplayName: &ws.DisplayName, Status: &secretStatus, @@ -614,7 +615,7 @@ func (s *APIServer) ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request, total := len(items) status := "success" - httputil.WriteJSON(w, http.StatusOK, api.WebhookSecretListResponse{ + httputil.WriteJSON(w, http.StatusOK, eventgateway.WebhookSecretListResponse{ Status: &status, TotalCount: &total, Secrets: &items, @@ -623,46 +624,46 @@ func (s *APIServer) ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request, // RegenerateWebSubAPISecret implements ServerInterface.RegenerateWebSubAPISecret // (POST /websub-apis/{id}/secrets/{secretName}/regenerate) -func (s *APIServer) RegenerateWebSubAPISecret(w http.ResponseWriter, r *http.Request, handle string, secretName string) { +func (s *WebSubServer) RegenerateWebSubAPISecret(w http.ResponseWriter, r *http.Request, handle string, secretName string) { log := middleware.GetLogger(r, s.logger) correlationID := middleware.GetCorrelationID(r) cfg, err := s.db.GetConfigByKindAndHandle(models.KindWebSubApi, handle) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } if storage.IsDatabaseUnavailableError(err) { - httputil.WriteJSON(w, http.StatusServiceUnavailable, api.ErrorResponse{Status: "error", Message: "Storage unavailable"}) + httputil.WriteJSON(w, http.StatusServiceUnavailable, eventgateway.ErrorResponse{Status: "error", Message: "Storage unavailable"}) return } log.Error("Failed to look up WebSub API", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) return } if cfg == nil { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } ws, plaintext, err := s.webhookSecretService.Regenerate(cfg.UUID, secretName, correlationID) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Webhook secret '%s' not found", secretName)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Webhook secret '%s' not found", secretName)}) return } log.Error("Failed to regenerate webhook secret", slog.String("handle", handle), slog.String("secret", secretName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to regenerate webhook secret"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to regenerate webhook secret"}) return } - secretStatus := api.WebhookSecretInfoStatus(ws.Status) - httputil.WriteJSON(w, http.StatusOK, api.WebhookSecretCreationResponse{ + secretStatus := eventgateway.WebhookSecretInfoStatus(ws.Status) + httputil.WriteJSON(w, http.StatusOK, eventgateway.WebhookSecretCreationResponse{ Status: "success", Message: "Webhook secret regenerated successfully", Secret: plaintext, - WebhookSecret: &api.WebhookSecretInfo{ + WebhookSecret: &eventgateway.WebhookSecretInfo{ Name: &ws.Name, DisplayName: &ws.DisplayName, Status: &secretStatus, @@ -674,36 +675,36 @@ func (s *APIServer) RegenerateWebSubAPISecret(w http.ResponseWriter, r *http.Req // DeleteWebSubAPISecret implements ServerInterface.DeleteWebSubAPISecret // (DELETE /websub-apis/{id}/secrets/{secretName}) -func (s *APIServer) DeleteWebSubAPISecret(w http.ResponseWriter, r *http.Request, handle string, secretName string) { +func (s *WebSubServer) DeleteWebSubAPISecret(w http.ResponseWriter, r *http.Request, handle string, secretName string) { log := middleware.GetLogger(r, s.logger) correlationID := middleware.GetCorrelationID(r) cfg, err := s.db.GetConfigByKindAndHandle(models.KindWebSubApi, handle) if err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } if storage.IsDatabaseUnavailableError(err) { - httputil.WriteJSON(w, http.StatusServiceUnavailable, api.ErrorResponse{Status: "error", Message: "Storage unavailable"}) + httputil.WriteJSON(w, http.StatusServiceUnavailable, eventgateway.ErrorResponse{Status: "error", Message: "Storage unavailable"}) return } log.Error("Failed to look up WebSub API", slog.String("handle", handle), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Internal error looking up WebSub API"}) return } if cfg == nil { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("WebSub API '%s' not found", handle)}) return } if err := s.webhookSecretService.Delete(cfg.UUID, secretName, correlationID); err != nil { if storage.IsNotFoundError(err) { - httputil.WriteJSON(w, http.StatusNotFound, api.ErrorResponse{Status: "error", Message: fmt.Sprintf("Webhook secret '%s' not found", secretName)}) + httputil.WriteJSON(w, http.StatusNotFound, eventgateway.ErrorResponse{Status: "error", Message: fmt.Sprintf("Webhook secret '%s' not found", secretName)}) return } log.Error("Failed to delete webhook secret", slog.String("handle", handle), slog.String("secret", secretName), slog.Any("error", err)) - httputil.WriteJSON(w, http.StatusInternalServerError, api.ErrorResponse{Status: "error", Message: "Failed to delete webhook secret"}) + httputil.WriteJSON(w, http.StatusInternalServerError, eventgateway.ErrorResponse{Status: "error", Message: "Failed to delete webhook secret"}) return } diff --git a/event-gateway/gateway-controller/pkg/hubtopic/deregistrar.go b/event-gateway/gateway-controller/pkg/hubtopic/deregistrar.go new file mode 100644 index 0000000000..867cd880ba --- /dev/null +++ b/event-gateway/gateway-controller/pkg/hubtopic/deregistrar.go @@ -0,0 +1,102 @@ +/* + * 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 hubtopic implements gateway-controller (core)'s +// restapi.SetWebSubTopicDeregistrar hook, moved out of core's +// pkg/service/restapi/service.go (deregisterWebSubTopics). +package hubtopic + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "sync" + "sync/atomic" + "time" + + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" + + eventgatewayconfig "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/config" +) + +// Deregistrar deregisters WebSub hub topics for a deleted WebSubApi config. +type Deregistrar struct { + deploymentService *utils.APIDeploymentService + httpClient *http.Client + eventGatewayConfig eventgatewayconfig.EventGatewayConfig +} + +// New creates a new Deregistrar. +func New(deploymentService *utils.APIDeploymentService, httpClient *http.Client, cfg eventgatewayconfig.EventGatewayConfig) *Deregistrar { + return &Deregistrar{ + deploymentService: deploymentService, + httpClient: httpClient, + eventGatewayConfig: cfg, + } +} + +// Deregister matches the signature required by restapi.SetWebSubTopicDeregistrar. +func (d *Deregistrar) Deregister(cfg *models.StoredConfig, log *slog.Logger) error { + topicsToUnregister := d.deploymentService.GetTopicsForDelete(*cfg) + + var deregErrs int32 + var wg sync.WaitGroup + + if len(topicsToUnregister) > 0 { + wg.Add(1) + go func(list []string) { + defer wg.Done() + log.Info("Starting topic deregistration", slog.Int("total_topics", len(list)), slog.String("api_id", cfg.UUID)) + var childWg sync.WaitGroup + for _, topic := range list { + childWg.Add(1) + go func(topic string) { + defer childWg.Done() + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(d.eventGatewayConfig.TimeoutSeconds)*time.Second) + defer cancel() + if err := d.deploymentService.UnregisterTopicWithHub(ctx, d.httpClient, topic, d.eventGatewayConfig.RouterHost, d.eventGatewayConfig.WebSubHubListenerPort, log); err != nil { + log.Error("Failed to deregister topic from WebSubHub", + slog.Any("error", err), + slog.String("topic", topic), + slog.String("api_id", cfg.UUID)) + atomic.AddInt32(&deregErrs, 1) + } else { + log.Info("Successfully deregistered topic from WebSubHub", + slog.String("topic", topic), + slog.String("api_id", cfg.UUID)) + } + }(topic) + } + childWg.Wait() + }(topicsToUnregister) + } + + wg.Wait() + + log.Info("Topic lifecycle operations completed", + slog.String("api_id", cfg.UUID), + slog.Int("deregistered", len(topicsToUnregister)), + slog.Int("deregister_errors", int(deregErrs))) + + if deregErrs > 0 { + return fmt.Errorf("topic lifecycle operations failed") + } + return nil +} diff --git a/event-gateway/gateway-controller/pkg/kindsupport/kindsupport.go b/event-gateway/gateway-controller/pkg/kindsupport/kindsupport.go new file mode 100644 index 0000000000..e56d8e0345 --- /dev/null +++ b/event-gateway/gateway-controller/pkg/kindsupport/kindsupport.go @@ -0,0 +1,261 @@ +/* + * 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 kindsupport registers this module's "WebSubApi"/"WebBrokerApi" kind +// support into gateway-controller (core)'s registries — the various +// Register* functions in pkg/storage, pkg/utils, and pkg/policy that core +// exposes as its abstraction layer for kinds it doesn't know about natively. +// Call Register() once during binary startup, before any deployment traffic +// is served. +package kindsupport + +import ( + "encoding/json" + "fmt" + "strings" + + commonconstants "github.com/wso2/api-platform/common/constants" + coreconfig "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/constants" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/policy" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" + eventgatewayconfig "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/policyhooks" +) + +// Register wires WebSubApi/WebBrokerApi kind support into every core registry +// that gateway-controller exposes for kinds it doesn't know about natively. +func Register() { + storage.RegisterKindUnmarshaler("WebSubApi", unmarshalWebSubAPI) + storage.RegisterKindUnmarshaler("WebBrokerApi", unmarshalWebBrokerApi) + + utils.RegisterKindDeployParser("WebSubApi", parseWebSubDeploy) + utils.RegisterKindDeployParser("WebBrokerApi", parseWebBrokerDeploy) + + utils.RegisterKindConfigValidator("WebSubApi", eventgatewayconfig.ValidateWebSubAPI) + utils.RegisterKindConfigValidator("WebBrokerApi", eventgatewayconfig.ValidateWebBrokerAPI) + + utils.RegisterKindTopicsForUpdate("WebSubApi", topicsForWebSubUpdate) + + utils.RegisterKindDisplayNameVersionExtractor("WebSubApi", displayNameVersion) + utils.RegisterKindDisplayNameVersionExtractor("WebBrokerApi", displayNameVersion) + + utils.RegisterKindVhostSentinelResolver(fmt.Sprintf("%T", eventgateway.WebSubAPI{}), resolveWebSubVhostSentinels) + utils.RegisterKindVhostSentinelResolver(fmt.Sprintf("%T", eventgateway.WebBrokerApi{}), resolveWebBrokerVhostSentinels) + + policy.RegisterEventGatewayPolicyChainBuilder(policyhooks.BuildPolicyChains) +} + +func unmarshalWebSubAPI(cfg *models.StoredConfig, jsonData string) error { + var config eventgateway.WebSubAPI + if err := json.Unmarshal([]byte(jsonData), &config); err != nil { + return fmt.Errorf("failed to unmarshal configuration: %w", err) + } + cfg.SourceConfiguration = config + cfg.Configuration = config + return nil +} + +func unmarshalWebBrokerApi(cfg *models.StoredConfig, jsonData string) error { + var config eventgateway.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 + return nil +} + +func annotationValue(annotations *map[string]string, key string) string { + if annotations == nil { + return "" + } + return (*annotations)[key] +} + +func parseWebSubDeploy(parser *coreconfig.Parser, data []byte, contentType string) (any, string, string, string, error) { + var webSubConfig eventgateway.WebSubAPI + if err := parser.Parse(data, contentType, &webSubConfig); err != nil { + return nil, "", "", "", err + } + handle := webSubConfig.Metadata.Name + kind := string(webSubConfig.Kind) + annotationArtifactID := annotationValue(webSubConfig.Metadata.Annotations, commonconstants.AnnotationArtifactID) + return webSubConfig, handle, kind, annotationArtifactID, nil +} + +func parseWebBrokerDeploy(parser *coreconfig.Parser, data []byte, contentType string) (any, string, string, string, error) { + var webBrokerConfig eventgateway.WebBrokerApi + if err := parser.Parse(data, contentType, &webBrokerConfig); err != nil { + return nil, "", "", "", err + } + handle := webBrokerConfig.Metadata.Name + kind := string(webBrokerConfig.Kind) + annotationArtifactID := annotationValue(webBrokerConfig.Metadata.Annotations, commonconstants.AnnotationArtifactID) + return webBrokerConfig, handle, kind, annotationArtifactID, nil +} + +func topicsForWebSubUpdate(tm *storage.TopicManager, apiConfig models.StoredConfig) ([]string, []string) { + topics := tm.GetAllByConfig(apiConfig.UUID) + topicsToRegister := []string{} + topicsToUnregister := []string{} + apiTopicsPerRevision := make(map[string]bool) + + webSubCfg, ok := apiConfig.Configuration.(eventgateway.WebSubAPI) + if !ok { + return topicsToRegister, topicsToUnregister + } + asyncData := webSubCfg.Spec + + var channels map[string]eventgateway.WebSubChannel + if asyncData.Channels != nil { + channels = *asyncData.Channels + } + for chName := range channels { + contextWithVersion := strings.ReplaceAll(asyncData.Context, "$version", asyncData.Version) + contextWithVersion = strings.TrimPrefix(contextWithVersion, "/") + contextWithVersion = strings.ReplaceAll(contextWithVersion, "/", "_") + name := strings.TrimPrefix(chName, "/") + modifiedTopic := fmt.Sprintf("%s_%s", contextWithVersion, name) + apiTopicsPerRevision[modifiedTopic] = true + } + + for _, topic := range topics { + if _, exists := apiTopicsPerRevision[topic]; !exists { + topicsToUnregister = append(topicsToUnregister, topic) + } + } + for topic := range apiTopicsPerRevision { + if tm.IsTopicExist(apiConfig.UUID, topic) { + continue + } + topicsToRegister = append(topicsToRegister, topic) + } + + return topicsToRegister, topicsToUnregister +} + +// UpdateTopicManager matches storage.ConfigStore.SetWebSubTopicUpdater's hook +// signature — call storage.SetWebSubTopicUpdater(kindsupport.UpdateTopicManager) +// once at startup. +func UpdateTopicManager(cfg *models.StoredConfig, tm *storage.TopicManager) error { + webSubCfg, ok := cfg.Configuration.(eventgateway.WebSubAPI) + if !ok { + return fmt.Errorf("configuration is not a WebSubAPI") + } + asyncData := webSubCfg.Spec + apiTopicsPerRevision := make(map[string]bool) + var channels map[string]eventgateway.WebSubChannel + if asyncData.Channels != nil { + channels = *asyncData.Channels + } + for chName := range channels { + contextWithVersion := strings.ReplaceAll(asyncData.Context, "$version", asyncData.Version) + contextWithVersion = strings.TrimPrefix(contextWithVersion, "/") + contextWithVersion = strings.ReplaceAll(contextWithVersion, "/", "_") + name := strings.TrimPrefix(chName, "/") + modifiedTopic := fmt.Sprintf("%s_%s", contextWithVersion, name) + tm.Add(cfg.UUID, modifiedTopic) + apiTopicsPerRevision[modifiedTopic] = true + } + for _, topic := range tm.GetAllByConfig(cfg.UUID) { + if _, exists := apiTopicsPerRevision[topic]; !exists { + tm.Remove(cfg.UUID, topic) + } + } + return nil +} + +func displayNameVersion(configuration any) (string, string, error) { + switch c := configuration.(type) { + case eventgateway.WebSubAPI: + return c.Spec.DisplayName, c.Spec.Version, nil + case eventgateway.WebBrokerApi: + return c.Spec.DisplayName, c.Spec.Version, nil + default: + return "", "", fmt.Errorf("configuration is not a WebSubAPI or WebBrokerApi (kind: %T)", configuration) + } +} + +func resolveWebSubVhostSentinels(cfg any, routerCfg *coreconfig.RouterConfig) (any, error) { + c, ok := cfg.(eventgateway.WebSubAPI) + if !ok { + return cfg, nil + } + 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 + } + return c, 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 + } + } + return c, nil +} + +func resolveWebBrokerVhostSentinels(cfg any, routerCfg *coreconfig.RouterConfig) (any, error) { + c, ok := cfg.(eventgateway.WebBrokerApi) + if !ok { + return cfg, nil + } + 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 + } + return c, 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 + } + } + return c, nil +} diff --git a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go b/event-gateway/gateway-controller/pkg/policyhooks/event_channel_translator.go similarity index 55% rename from gateway/gateway-controller/pkg/policyxds/event_channel_translator.go rename to event-gateway/gateway-controller/pkg/policyhooks/event_channel_translator.go index 9e66d9c91e..198402a135 100644 --- a/gateway/gateway-controller/pkg/policyxds/event_channel_translator.go +++ b/event-gateway/gateway-controller/pkg/policyhooks/event_channel_translator.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * 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 @@ -16,20 +16,47 @@ * under the License. */ -package policyxds +// Package policyhooks implements gateway-controller (core)'s +// policyxds.EventChannelTranslator interface and policy.RegisterEventGatewayPolicyChainBuilder +// hook, moved out of core's pkg/policyxds/event_channel_translator.go and +// pkg/policy/builder.go (the WebSubApi case). +package policyhooks import ( "log/slog" "sort" "github.com/envoyproxy/go-control-plane/pkg/cache/types" + api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/policy" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/policyxds" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/xds" + versionutil "github.com/wso2/api-platform/common/version" + policyv1alpha "github.com/wso2/api-platform/sdk/core/policy/v1alpha2" + policyenginev1 "github.com/wso2/api-platform/sdk/core/policyengine" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" ) +// EventChannelTranslator implements policyxds.EventChannelTranslator. +type EventChannelTranslator struct { + logger *slog.Logger +} + +// New creates a new EventChannelTranslator. +func New(logger *slog.Logger) *EventChannelTranslator { + return &EventChannelTranslator{logger: logger} +} + +var _ policyxds.EventChannelTranslator = (*EventChannelTranslator)(nil) + // TranslateWebSubApisToEventChannelConfigs translates WebSubApi StoredConfigs into // EventChannelConfig xDS resources for the event gateway runtime. -func (t *Translator) TranslateWebSubApisToEventChannelConfigs(configs []*models.StoredConfig) map[string]types.Resource { +func (t *EventChannelTranslator) TranslateWebSubApisToEventChannelConfigs(configs []*models.StoredConfig) map[string]types.Resource { resources := make(map[string]types.Resource) for _, cfg := range configs { @@ -40,7 +67,7 @@ func (t *Translator) TranslateWebSubApisToEventChannelConfigs(configs []*models. continue } - webSubCfg, ok := cfg.Configuration.(api.WebSubAPI) + webSubCfg, ok := cfg.Configuration.(eventgateway.WebSubAPI) if !ok { t.logger.Warn("Failed to type-assert WebSubApi configuration", slog.String("uuid", cfg.UUID)) @@ -67,7 +94,7 @@ func (t *Translator) TranslateWebSubApisToEventChannelConfigs(configs []*models. // TranslateWebBrokerApisToEventChannelConfigs translates WebBrokerApi StoredConfigs into // EventChannelConfig xDS resources for the event gateway runtime. -func (t *Translator) TranslateWebBrokerApisToEventChannelConfigs(configs []*models.StoredConfig) map[string]types.Resource { +func (t *EventChannelTranslator) TranslateWebBrokerApisToEventChannelConfigs(configs []*models.StoredConfig) map[string]types.Resource { resources := make(map[string]types.Resource) for _, cfg := range configs { @@ -78,7 +105,7 @@ func (t *Translator) TranslateWebBrokerApisToEventChannelConfigs(configs []*mode continue } - webBrokerCfg, ok := cfg.Configuration.(api.WebBrokerApi) + webBrokerCfg, ok := cfg.Configuration.(eventgateway.WebBrokerApi) if !ok { t.logger.Warn("Failed to type-assert WebBrokerApi configuration", slog.String("uuid", cfg.UUID)) @@ -103,11 +130,10 @@ func (t *Translator) TranslateWebBrokerApisToEventChannelConfigs(configs []*mode return resources } -func (t *Translator) buildEventChannelResourceForWebSub(uuid string, webSubCfg *api.WebSubAPI) (types.Resource, error) { +func (t *EventChannelTranslator) buildEventChannelResourceForWebSub(uuid string, webSubCfg *eventgateway.WebSubAPI) (types.Resource, error) { spec := webSubCfg.Spec - // Build channels list from channels, including per-channel policies. - var channels map[string]api.WebSubChannel + var channels map[string]eventgateway.WebSubChannel if spec.Channels != nil { channels = *spec.Channels } @@ -144,7 +170,6 @@ func (t *Translator) buildEventChannelResourceForWebSub(uuid string, webSubCfg * channelEntries = append(channelEntries, chEntry) } - // policies maps to API-level policy chains. subscribePolicies := []interface{}{} unsubscribePolicies := []interface{}{} inboundPolicies := []interface{}{} @@ -182,13 +207,12 @@ func (t *Translator) buildEventChannelResourceForWebSub(uuid string, webSubCfg * }, } - return toAnyResource(data, EventChannelConfigTypeURL) + return policyxds.ToAnyResource(data, policyxds.EventChannelConfigTypeURL) } -func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBrokerCfg *api.WebBrokerApi) (types.Resource, error) { +func (t *EventChannelTranslator) buildEventChannelResourceForWebBroker(uuid string, webBrokerCfg *eventgateway.WebBrokerApi) (types.Resource, error) { spec := webBrokerCfg.Spec - // Build receiver configuration receiver := map[string]interface{}{ "name": spec.Receiver.Name, "type": spec.Receiver.Type, @@ -197,19 +221,12 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke receiver["properties"] = *spec.Receiver.Properties } - // Build broker-driver configuration brokerDriver := map[string]interface{}{ "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.Broker.Name, - "brokerDriverType", spec.Broker.Type) - // Build API-level policies from AllChannels var apiOnConnectionInit []interface{} var apiOnProduce []interface{} var apiOnConsume []interface{} @@ -226,7 +243,6 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke } } - // 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 { @@ -234,7 +250,6 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke var channelOnProduce []interface{} var channelOnConsume []interface{} - // Extract policies from channel-level policy groups if channelConfig.OnConnectionInit != nil && channelConfig.OnConnectionInit.Policies != nil { channelOnConnectionInit = buildPolicyList(channelConfig.OnConnectionInit.Policies) } @@ -245,24 +260,20 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke channelOnConsume = buildPolicyList(channelConfig.OnConsume.Policies) } - // 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 (flattened structure) channelEntry["policies"] = map[string]interface{}{ "on_connection_init": channelOnConnectionInit, "on_produce": channelOnProduce, @@ -289,10 +300,10 @@ func (t *Translator) buildEventChannelResourceForWebBroker(uuid string, webBroke "channels": channels, } - return toAnyResource(data, EventChannelConfigTypeURL) + return policyxds.ToAnyResource(data, policyxds.EventChannelConfigTypeURL) } -func buildPolicyList(policies *[]api.Policy) []interface{} { +func buildPolicyList(policies *[]eventgateway.Policy) []interface{} { if policies == nil || len(*policies) == 0 { return []interface{}{} } @@ -310,3 +321,89 @@ func buildPolicyList(policies *[]api.Policy) []interface{} { } return result } + +// BuildPolicyChains implements the hook registered via +// policy.RegisterEventGatewayPolicyChainBuilder, building per-channel policy +// chains for WebSubApi configs (RestApi is handled natively by core). +func BuildPolicyChains(cfg *models.StoredConfig, routerConfig *config.RouterConfig, systemConfig *config.Config, policyDefinitions map[string]models.PolicyDefinition) []policyenginev1.PolicyChain { + cfgTyped, ok := cfg.Configuration.(eventgateway.WebSubAPI) + if !ok { + return nil + } + + latestVersions := config.BuildLatestVersionIndex(policyDefinitions) + var routes []policyenginev1.PolicyChain + + apiData := cfgTyped.Spec + var channels map[string]eventgateway.WebSubChannel + if apiData.Channels != nil { + channels = *apiData.Channels + } + for chName, ch := range channels { + var finalPolicies []policyenginev1.PolicyInstance + + if apiData.AllChannels != nil && apiData.AllChannels.OnSubscription != nil && apiData.AllChannels.OnSubscription.Policies != nil { + finalPolicies = make([]policyenginev1.PolicyInstance, 0, len(*apiData.AllChannels.OnSubscription.Policies)) + for _, p := range *apiData.AllChannels.OnSubscription.Policies { + resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, p.Name, p.Version) + if err != nil { + slog.Error("Failed to resolve policy version for all-channel subscription policy", "policy_name", p.Name, "error", err) + continue + } + finalPolicies = append(finalPolicies, policy.ConvertAPIPolicyToModel(api.Policy(p), policyv1alpha.LevelAPI, versionutil.MajorVersion(resolved))) + } + } + + if ch.OnSubscription != nil && ch.OnSubscription.Policies != nil && len(*ch.OnSubscription.Policies) > 0 { + for _, opPolicy := range *ch.OnSubscription.Policies { + resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, opPolicy.Name, opPolicy.Version) + if err != nil { + slog.Error("Failed to resolve policy version for channel-level policy", "policy_name", opPolicy.Name, "channel_name", chName, "error", err) + continue + } + finalPolicies = append(finalPolicies, policy.ConvertAPIPolicyToModel(api.Policy(opPolicy), policyv1alpha.LevelRoute, versionutil.MajorVersion(resolved))) + } + } + + routeKey := xds.GenerateRouteName("SUB", apiData.Context, apiData.Version, chName, routerConfig.GatewayHost) + props := make(map[string]any) + injectedPolicies := utils.InjectSystemPolicies(finalPolicies, systemConfig, props) + + routes = append(routes, policyenginev1.PolicyChain{ + RouteKey: routeKey, + Policies: injectedPolicies, + }) + + var unsubPolicies []policyenginev1.PolicyInstance + if apiData.AllChannels != nil && apiData.AllChannels.OnUnsubscription != nil && apiData.AllChannels.OnUnsubscription.Policies != nil { + unsubPolicies = make([]policyenginev1.PolicyInstance, 0, len(*apiData.AllChannels.OnUnsubscription.Policies)) + for _, p := range *apiData.AllChannels.OnUnsubscription.Policies { + resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, p.Name, p.Version) + if err != nil { + slog.Error("Failed to resolve policy version for all-channel unsubscription policy", "policy_name", p.Name, "error", err) + continue + } + unsubPolicies = append(unsubPolicies, policy.ConvertAPIPolicyToModel(api.Policy(p), policyv1alpha.LevelAPI, versionutil.MajorVersion(resolved))) + } + } + if ch.OnUnsubscription != nil && ch.OnUnsubscription.Policies != nil && len(*ch.OnUnsubscription.Policies) > 0 { + for _, opPolicy := range *ch.OnUnsubscription.Policies { + resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, opPolicy.Name, opPolicy.Version) + if err != nil { + slog.Error("Failed to resolve policy version for channel-level unsubscription policy", "policy_name", opPolicy.Name, "channel_name", chName, "error", err) + continue + } + unsubPolicies = append(unsubPolicies, policy.ConvertAPIPolicyToModel(api.Policy(opPolicy), policyv1alpha.LevelRoute, versionutil.MajorVersion(resolved))) + } + } + unsubRouteKey := xds.GenerateRouteName("UNSUB", apiData.Context, apiData.Version, chName, routerConfig.GatewayHost) + unsubProps := make(map[string]any) + injectedUnsubPolicies := utils.InjectSystemPolicies(unsubPolicies, systemConfig, unsubProps) + routes = append(routes, policyenginev1.PolicyChain{ + RouteKey: unsubRouteKey, + Policies: injectedUnsubPolicies, + }) + } + + return routes +} diff --git a/event-gateway/gateway-controller/pkg/translator/hooks.go b/event-gateway/gateway-controller/pkg/translator/hooks.go new file mode 100644 index 0000000000..ee8af9a512 --- /dev/null +++ b/event-gateway/gateway-controller/pkg/translator/hooks.go @@ -0,0 +1,467 @@ +/* + * 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 translator implements gateway-controller (core)'s +// xds.EventGatewayXDSHooks interface, moved out of core's +// pkg/xds/translator.go (translateAsyncAPIConfig, createInternalListenerForWebSubHub, +// createDynamicFwdListenerForWebSubHub, createDynamicForwardProxyCluster). +package translator + +import ( + "fmt" + "log/slog" + "net/url" + "strings" + "time" + + cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + dfpcluster "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3" + common_dfp "github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3" + dfpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3" + hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + router "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/constants" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/xds" + + eventgateway "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/api/eventgateway" + eventgatewayconfig "github.com/wso2/api-platform/event-gateway/gateway-controller/pkg/config" +) + +const ( + // WebSubHubInternalClusterName mirrors core's xds.WebSubHubInternalClusterName. + WebSubHubInternalClusterName = "WEBSUBHUB_INTERNAL_CLUSTER" + // DynamicForwardProxyClusterName mirrors core's xds.DynamicForwardProxyClusterName. + DynamicForwardProxyClusterName = "dynamic-forward-proxy-cluster" + // dynamicForwardProxyFilterName is Envoy's registered name for the dynamic forward proxy HTTP filter. + dynamicForwardProxyFilterName = "envoy.filters.http.dynamic_forward_proxy" +) + +// Hooks implements xds.EventGatewayXDSHooks. +type Hooks struct { + eventGatewayConfig eventgatewayconfig.EventGatewayConfig +} + +// New creates a new Hooks instance. +func New(cfg eventgatewayconfig.EventGatewayConfig) *Hooks { + return &Hooks{eventGatewayConfig: cfg} +} + +var _ xds.EventGatewayXDSHooks = (*Hooks)(nil) + +// BuildHubResources returns the dynamic-forward-proxy cluster, WebSubHub +// cluster, and internal listener(s) needed for WebSubHub connectivity. +func (h *Hooks) BuildHubResources(t *xds.Translator, httpsEnabled bool) ([]*cluster.Cluster, []*listener.Listener, error) { + var clusters []*cluster.Cluster + var listeners []*listener.Listener + + dynamicForwardProxyCluster := h.createDynamicForwardProxyCluster(t) + if dynamicForwardProxyCluster == nil { + return nil, nil, fmt.Errorf("failed to create dynamic forward proxy cluster") + } + clusters = append(clusters, dynamicForwardProxyCluster) + + dynamicProxyListener, err := h.createDynamicFwdListenerForWebSubHub(t, httpsEnabled) + if err != nil { + return nil, nil, fmt.Errorf("failed to create WebSub listener: %w", err) + } + listeners = append(listeners, dynamicProxyListener) + + parsedURL, err := url.Parse(h.eventGatewayConfig.WebSubHubURL) + if err != nil { + return nil, nil, fmt.Errorf("invalid upstream URL: %w", err) + } + if parsedURL.Port() == "" { + parsedURL.Host = fmt.Sprintf("%s:%d", parsedURL.Hostname(), h.eventGatewayConfig.WebSubHubPort) + } + if parsedURL.Scheme == "" { + parsedURL.Scheme = "http" + } + websubhubCluster := t.CreateCluster(constants.WEBSUBHUB_INTERNAL_CLUSTER_NAME, parsedURL, nil, nil) + clusters = append(clusters, websubhubCluster) + + websubInternalListener, err := h.createInternalListenerForWebSubHub(t, false) + if err != nil { + return nil, nil, fmt.Errorf("failed to create WebSub internal listener: %w", err) + } + listeners = append(listeners, websubInternalListener) + + if httpsEnabled { + httpsListener, err := h.createInternalListenerForWebSubHub(t, true) + if err != nil { + return nil, nil, fmt.Errorf("failed to create HTTPS listener: %w", err) + } + listeners = append(listeners, httpsListener) + } + + return clusters, listeners, nil +} + +// TranslateWebSubAPI builds routes/clusters for a single WebSubApi config. +func (h *Hooks) TranslateWebSubAPI(t *xds.Translator, cfg *models.StoredConfig, allConfigs []*models.StoredConfig) ([]*route.Route, []*cluster.Cluster, error) { + webSubCfg, ok := cfg.Configuration.(eventgateway.WebSubAPI) + if !ok { + return nil, nil, fmt.Errorf("configuration is not a WebSubAPI") + } + apiData := webSubCfg.Spec + + clusters := []*cluster.Cluster{} + + mainClusterName := constants.WEBSUBHUB_INTERNAL_CLUSTER_NAME + parsedMainURL, err := url.Parse(h.eventGatewayConfig.WebSubHubURL) + if err != nil { + return nil, nil, fmt.Errorf("invalid upstream URL: %w", err) + } + if parsedMainURL.Path == "" { + parsedMainURL.Path = constants.WEBSUB_PATH + } + + routesList := make([]*route.Route, 0) + mainRoutesList := make([]*route.Route, 0) + + effectiveMainVHost := t.Config().Router.VHosts.Main.Default + if apiData.Vhosts != nil { + if strings.TrimSpace(apiData.Vhosts.Main) != "" { + effectiveMainVHost = apiData.Vhosts.Main + } + } + apiProjectID := xds.ExtractProjectIDFromConfig(cfg) + + if apiData.Channels != nil { + for chName := range *apiData.Channels { + if !strings.HasPrefix(chName, "/") { + chName = "/" + chName + } + r := t.CreateRoutePerTopic(cfg.UUID, apiData.DisplayName, apiData.Version, apiData.Context, "SUB", chName, + mainClusterName, effectiveMainVHost, cfg.Kind, apiProjectID) + mainRoutesList = append(mainRoutesList, r) + rUnsub := t.CreateRoutePerTopic(cfg.UUID, apiData.DisplayName, apiData.Version, apiData.Context, "UNSUB", chName, + mainClusterName, effectiveMainVHost, cfg.Kind, apiProjectID) + mainRoutesList = append(mainRoutesList, rUnsub) + } + } + templateHandle := t.ExtractTemplateHandle(cfg, allConfigs) + providerName := t.ExtractProviderName(cfg, allConfigs) + r := t.CreateRoute(cfg.UUID, apiData.DisplayName, apiData.Version, apiData.Context, "POST", constants.WEBSUB_PATH, mainClusterName, "/", effectiveMainVHost, cfg.Kind, templateHandle, providerName, nil, apiProjectID, nil, false, nil) + routesList = append(routesList, mainRoutesList...) + routesList = append(routesList, r) + + return routesList, clusters, nil +} + +func (h *Hooks) createInternalListenerForWebSubHub(t *xds.Translator, isHTTPS bool) (*listener.Listener, error) { + routeConfig := &route.RouteConfiguration{ + Name: "websubhub-internal-route", + VirtualHosts: []*route.VirtualHost{{ + Name: "WEBSUBHUB_INTERNAL_VHOST", + Domains: []string{"*"}, + Routes: []*route.Route{{ + Match: &route.RouteMatch{PathSpecifier: &route.RouteMatch_Path{Path: "/websubhub/operations"}}, + Action: &route.Route_Route{Route: &route.RouteAction{ + ClusterSpecifier: &route.RouteAction_Cluster{Cluster: WebSubHubInternalClusterName}, + Timeout: durationpb.New(30 * time.Second), + PrefixRewrite: "/hub", + }}, + }}, + }}, + } + + routerCfg := &router.Router{} + routerAny, err := anypb.New(routerCfg) + if err != nil { + return nil, fmt.Errorf("failed to create router config: %w", err) + } + + httpFilters := make([]*hcm.HttpFilter, 0) + + extProcFilter, err := t.CreateExtProcFilter() + if err != nil { + return nil, fmt.Errorf("failed to create ext_proc filter: %w", err) + } + httpFilters = append(httpFilters, extProcFilter) + + luaFilter, err := t.CreateLuaFilter() + if err != nil { + return nil, fmt.Errorf("failed to create lua filter: %w", err) + } + httpFilters = append(httpFilters, luaFilter) + + httpFilters = append(httpFilters, &hcm.HttpFilter{ + Name: wellknown.Router, + ConfigType: &hcm.HttpFilter_TypedConfig{ + TypedConfig: routerAny, + }, + }) + + manager := &hcm.HttpConnectionManager{ + CodecType: hcm.HttpConnectionManager_AUTO, + StatPrefix: "http", + GenerateRequestId: wrapperspb.Bool(true), + RouteSpecifier: &hcm.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, + HttpFilters: httpFilters, + } + + if t.RouterConfig().AccessLogs.Enabled { + accessLogs, err := t.CreateAccessLogConfig() + if err != nil { + return nil, fmt.Errorf("failed to create access log config: %w", err) + } + manager.AccessLog = accessLogs + } + + tracingConfig, err := t.CreateTracingConfig() + if err != nil { + return nil, fmt.Errorf("failed to create tracing config: %w", err) + } + if tracingConfig != nil { + manager.Tracing = tracingConfig + } + + pbst, err := anypb.New(manager) + if err != nil { + return nil, err + } + + var listenerName string + var port uint32 + if isHTTPS { + listenerName = fmt.Sprintf("listener_https_%d", constants.WEBSUB_HUB_INTERNAL_HTTPS_PORT) + port = uint32(constants.WEBSUB_HUB_INTERNAL_HTTPS_PORT) + } else { + listenerName = fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_INTERNAL_HTTP_PORT) + port = uint32(constants.WEBSUB_HUB_INTERNAL_HTTP_PORT) + } + + filterChain := &listener.FilterChain{ + Filters: []*listener.Filter{{ + Name: wellknown.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: pbst, + }, + }}, + } + + if isHTTPS { + tlsContext, err := t.CreateDownstreamTLSContext() + if err != nil { + return nil, fmt.Errorf("failed to create downstream TLS context: %w", err) + } + + tlsContextAny, err := anypb.New(tlsContext) + if err != nil { + return nil, fmt.Errorf("failed to marshal downstream TLS context: %w", err) + } + + filterChain.TransportSocket = &core.TransportSocket{ + Name: "envoy.transport_sockets.tls", + ConfigType: &core.TransportSocket_TypedConfig{ + TypedConfig: tlsContextAny, + }, + } + } + + return &listener.Listener{ + Name: listenerName, + Address: &core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + Protocol: core.SocketAddress_TCP, + Address: "0.0.0.0", + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: port, + }, + }, + }, + }, + FilterChains: []*listener.FilterChain{filterChain}, + }, nil +} + +func (h *Hooks) createDynamicFwdListenerForWebSubHub(t *xds.Translator, isHTTPS bool) (*listener.Listener, error) { + parsedHubURL, err := url.Parse(h.eventGatewayConfig.WebSubHubURL) + if err != nil { + return nil, fmt.Errorf("invalid upstream URL: %w", err) + } + + dynamicForwardProxyRouteConfig := &route.RouteConfiguration{ + Name: "dynamic-forward-proxy-routing", + VirtualHosts: []*route.VirtualHost{{ + Name: "DYNAMIC_FORWARD_PROXY_VHOST_WEBSUBHUB", + Domains: []string{parsedHubURL.Host}, + RequestHeadersToRemove: []string{xds.EnvoyOriginalPathHeader}, + Routes: []*route.Route{{ + Match: &route.RouteMatch{PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/"}}, + Action: &route.Route_Route{Route: &route.RouteAction{ + ClusterSpecifier: &route.RouteAction_Cluster{Cluster: DynamicForwardProxyClusterName}, + Timeout: durationpb.New(30 * time.Second), + RetryPolicy: &route.RetryPolicy{ + RetryOn: "5xx,reset,connect-failure,refused-stream", + NumRetries: wrapperspb.UInt32(1), + }, + }}, + }}, + }}, + } + httpFilters := make([]*hcm.HttpFilter, 0) + + extProcFilter, err := t.CreateExtProcFilter() + if err != nil { + return nil, fmt.Errorf("failed to create ext_proc filter: %w", err) + } + httpFilters = append(httpFilters, extProcFilter) + + luaFilter, err := t.CreateLuaFilter() + if err != nil { + return nil, fmt.Errorf("failed to create lua filter: %w", err) + } + httpFilters = append(httpFilters, luaFilter) + + dnsCacheConfig := &common_dfp.DnsCacheConfig{ + Name: "dynamic_forward_proxy_cache", + DnsRefreshRate: durationpb.New(60 * time.Second), + HostTtl: durationpb.New(300 * time.Second), + DnsLookupFamily: cluster.Cluster_V4_PREFERRED, + MaxHosts: &wrapperspb.UInt32Value{Value: 1024}, + } + + dfpFilterConfig := &dfpv3.FilterConfig{ + ImplementationSpecifier: &dfpv3.FilterConfig_DnsCacheConfig{ + DnsCacheConfig: dnsCacheConfig, + }, + } + dynamicFwdAny, err := anypb.New(dfpFilterConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal dynamic forward proxy config: %w", err) + } + + routerCfg := &router.Router{} + routerAny, err := anypb.New(routerCfg) + if err != nil { + return nil, fmt.Errorf("failed to marshal router config: %w", err) + } + + httpFilters = append(httpFilters, &hcm.HttpFilter{ + Name: dynamicForwardProxyFilterName, + ConfigType: &hcm.HttpFilter_TypedConfig{ + TypedConfig: dynamicFwdAny, + }, + }) + + httpFilters = append(httpFilters, &hcm.HttpFilter{ + Name: wellknown.Router, + ConfigType: &hcm.HttpFilter_TypedConfig{ + TypedConfig: routerAny, + }, + }) + + httpConnManager := &hcm.HttpConnectionManager{ + CodecType: hcm.HttpConnectionManager_AUTO, + StatPrefix: "http", + GenerateRequestId: wrapperspb.Bool(true), + RouteSpecifier: &hcm.HttpConnectionManager_RouteConfig{ + RouteConfig: dynamicForwardProxyRouteConfig, + }, + HttpFilters: httpFilters, + } + + if t.RouterConfig().AccessLogs.Enabled { + accessLogs, err := t.CreateAccessLogConfig() + if err != nil { + return nil, fmt.Errorf("failed to create access log config: %w", err) + } + httpConnManager.AccessLog = accessLogs + } + + tracingCfgDFP, err := t.CreateTracingConfig() + if err != nil { + return nil, fmt.Errorf("failed to create tracing config: %w", err) + } + if tracingCfgDFP != nil { + httpConnManager.Tracing = tracingCfgDFP + } + + pbst, err := anypb.New(httpConnManager) + if err != nil { + return nil, err + } + + var listenerName string + var port uint32 + if isHTTPS { + listenerName = fmt.Sprintf("listener_https_%d", constants.WEBSUB_HUB_DYNAMIC_HTTPS_PORT) + port = uint32(constants.WEBSUB_HUB_DYNAMIC_HTTPS_PORT) + } else { + listenerName = fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_DYNAMIC_HTTP_PORT) + port = uint32(constants.WEBSUB_HUB_DYNAMIC_HTTP_PORT) + } + + filterChain := &listener.FilterChain{ + Filters: []*listener.Filter{{ + Name: wellknown.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: pbst, + }, + }}, + } + + return &listener.Listener{ + Name: listenerName, + Address: &core.Address{Address: &core.Address_SocketAddress{SocketAddress: &core.SocketAddress{ + Protocol: core.SocketAddress_TCP, + Address: "0.0.0.0", + PortSpecifier: &core.SocketAddress_PortValue{PortValue: port}, + }}}, + FilterChains: []*listener.FilterChain{filterChain}, + }, nil +} + +func (h *Hooks) createDynamicForwardProxyCluster(t *xds.Translator) *cluster.Cluster { + clusterConfig := &dfpcluster.ClusterConfig{} + clusterTypeAny, err := anypb.New(clusterConfig) + if err != nil { + t.Logger().Error("Failed to marshal dynamic forward proxy cluster config", slog.Any("error", err)) + return nil + } + + return &cluster.Cluster{ + Name: DynamicForwardProxyClusterName, + ConnectTimeout: durationpb.New(5 * time.Second), + LbPolicy: cluster.Cluster_CLUSTER_PROVIDED, + ClusterDiscoveryType: &cluster.Cluster_ClusterType{ + ClusterType: &cluster.Cluster_CustomClusterType{ + Name: "envoy.clusters.dynamic_forward_proxy", + TypedConfig: clusterTypeAny, + }, + }, + UpstreamConnectionOptions: &cluster.UpstreamConnectionOptions{ + TcpKeepalive: &core.TcpKeepalive{ + KeepaliveTime: &wrapperspb.UInt32Value{Value: 300}, + }, + }, + } +} diff --git a/gateway/gateway-controller/pkg/utils/webhook_secret.go b/event-gateway/gateway-controller/pkg/webhooksecretservice/webhook_secret.go similarity index 92% rename from gateway/gateway-controller/pkg/utils/webhook_secret.go rename to event-gateway/gateway-controller/pkg/webhooksecretservice/webhook_secret.go index b7223471a6..870189465f 100644 --- a/gateway/gateway-controller/pkg/utils/webhook_secret.go +++ b/event-gateway/gateway-controller/pkg/webhooksecretservice/webhook_secret.go @@ -16,7 +16,14 @@ * under the License. */ -package utils +// Package webhooksecretservice manages per-API HMAC secrets for the +// websub-hmac-auth policy. Moved out of gateway-controller (core) +// pkg/utils/webhook_secret.go — the underlying DB row model +// (models.WebhookSecret) and storage.Storage CRUD methods stay in core since +// they are generic storage-layer infrastructure that pkg/storage itself +// depends on; only this service (encryption + in-memory store sync + event +// publication, all WebSub-specific) moves. +package webhooksecretservice import ( "crypto/rand" @@ -65,7 +72,13 @@ func NewWebhookSecretService( if db == nil { panic("WebhookSecretService requires non-nil storage") } - trimmedGatewayID := requireReplicaSyncWiring("WebhookSecretService", eventHub, gatewayID) + if eventHub == nil { + panic("WebhookSecretService requires non-nil EventHub") + } + trimmedGatewayID := strings.TrimSpace(gatewayID) + if trimmedGatewayID == "" { + panic("WebhookSecretService requires non-empty gateway ID") + } return &WebhookSecretService{ db: db, providerManager: providerManager, diff --git a/gateway/gateway-controller/pkg/webhooksecretxds/snapshot.go b/event-gateway/gateway-controller/pkg/webhooksecretxds/snapshot.go similarity index 90% rename from gateway/gateway-controller/pkg/webhooksecretxds/snapshot.go rename to event-gateway/gateway-controller/pkg/webhooksecretxds/snapshot.go index 73429f72f9..4b0014ab86 100644 --- a/gateway/gateway-controller/pkg/webhooksecretxds/snapshot.go +++ b/event-gateway/gateway-controller/pkg/webhooksecretxds/snapshot.go @@ -31,15 +31,17 @@ import ( "github.com/envoyproxy/go-control-plane/pkg/cache/v3" "github.com/wso2/api-platform/common/webhooksecret" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/logger" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/policyxds" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/structpb" ) -const ( - // WebhookSecretStateTypeURL is the xDS type URL for webhook secret state snapshots. - WebhookSecretStateTypeURL = "api-platform.wso2.org/v1.WebhookSecretState" -) +// WebhookSecretStateTypeURL is the xDS type URL for webhook secret state +// snapshots — re-exported from core's policyxds package, which is the single +// source of truth so core's CombinedCache dispatch and this producer never +// drift apart. +const WebhookSecretStateTypeURL = policyxds.WebhookSecretStateTypeURL // SnapshotManager maintains an xDS LinearCache for webhook secret state and // rebuilds it whenever the in-memory WebhookSecretStore changes. diff --git a/gateway/gateway-controller/Dockerfile b/gateway/gateway-controller/Dockerfile index 8fa83a90f8..7751e6fc46 100644 --- a/gateway/gateway-controller/Dockerfile +++ b/gateway/gateway-controller/Dockerfile @@ -4,7 +4,6 @@ FROM --platform=$BUILDPLATFORM golang:1.26.5-bookworm AS builder ARG TARGETARCH ARG VERSION=0.0.1-SNAPSHOT ARG GIT_COMMIT=unknown -ARG FUNCTIONALITY_TYPE=regular # Coverage build argument - set to "true" for coverage-instrumented builds ARG ENABLE_COVERAGE=false @@ -63,7 +62,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \ export GOARCH=${TARGETARCH} && \ BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) && \ VERSION_PKG=github.com/wso2/api-platform/gateway/gateway-controller/pkg/version && \ - LDFLAGS="-X ${VERSION_PKG}.Version=${VERSION} -X ${VERSION_PKG}.FunctionalityType=${FUNCTIONALITY_TYPE} -X ${VERSION_PKG}.GitCommit=${GIT_COMMIT} -X ${VERSION_PKG}.BuildDate=${BUILD_DATE}" && \ + LDFLAGS="-X ${VERSION_PKG}.Version=${VERSION} -X ${VERSION_PKG}.FunctionalityType=regular -X ${VERSION_PKG}.GitCommit=${GIT_COMMIT} -X ${VERSION_PKG}.BuildDate=${BUILD_DATE}" && \ if [ "$ENABLE_DEBUG" = "true" ]; then \ go build $COVER_FLAG \ -gcflags "all=-N -l" \ diff --git a/gateway/gateway-controller/Makefile b/gateway/gateway-controller/Makefile index 4df145bcb9..37ad2490ba 100644 --- a/gateway/gateway-controller/Makefile +++ b/gateway/gateway-controller/Makefile @@ -20,11 +20,10 @@ # Version management VERSION ?= $(shell cat ../VERSION 2>/dev/null || echo "0.0.1-SNAPSHOT") -FUNCTIONALITY_TYPE ?= regular GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") VERSION_PKG := github.com/wso2/api-platform/gateway/gateway-controller/pkg/version -LDFLAGS := -X $(VERSION_PKG).Version=$(VERSION) -X $(VERSION_PKG).FunctionalityType=$(FUNCTIONALITY_TYPE) -X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) -X $(VERSION_PKG).BuildDate=$(BUILD_DATE) +LDFLAGS := -X $(VERSION_PKG).Version=$(VERSION) -X $(VERSION_PKG).FunctionalityType=regular -X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) -X $(VERSION_PKG).BuildDate=$(BUILD_DATE) # Docker image configuration DOCKER_REGISTRY ?= ghcr.io/wso2/api-platform @@ -85,7 +84,6 @@ build: generate-server-code test ## Build Docker image using buildx --build-context policies=$(POLICIES_BUILD_CONTEXT) \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ - --build-arg FUNCTIONALITY_TYPE=$(FUNCTIONALITY_TYPE) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ --target production \ -t $(IMAGE_NAME):$(VERSION) \ @@ -114,7 +112,6 @@ build-and-push-multiarch: ## Build and push multi-architecture Docker image (lin --build-context target=target \ --platform linux/amd64,linux/arm64 \ --build-arg VERSION=$(VERSION) \ - --build-arg FUNCTIONALITY_TYPE=$(FUNCTIONALITY_TYPE) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ --target production \ -t $(IMAGE_NAME):$(VERSION) \ @@ -135,7 +132,6 @@ build-coverage-image: test ## Build coverage-instrumented gateway-controller ima --build-context policies=$(POLICIES_BUILD_CONTEXT) \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ - --build-arg FUNCTIONALITY_TYPE=$(FUNCTIONALITY_TYPE) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ --build-arg ENABLE_COVERAGE=true \ --target production \ @@ -156,7 +152,6 @@ build-debug: ## Build debug image for remote debugging with dlv (VS Code attach --build-context policies=$(POLICIES_BUILD_CONTEXT) \ --build-context target=target \ --build-arg VERSION=$(VERSION) \ - --build-arg FUNCTIONALITY_TYPE=$(FUNCTIONALITY_TYPE) \ --build-arg GIT_COMMIT=$(GIT_COMMIT) \ --build-arg ENABLE_DEBUG=true \ --target debug \ diff --git a/gateway/gateway-controller/api/management-openapi.yaml b/gateway/gateway-controller/api/management-openapi.yaml index 3f256d95ac..0768387e3a 100644 --- a/gateway/gateway-controller/api/management-openapi.yaml +++ b/gateway/gateway-controller/api/management-openapi.yaml @@ -851,1135 +851,6 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" - /websub-apis: - post: - summary: Create a new WebSubAPI - description: Add a new WebSubAPI to the Gateway. - operationId: createWebSubAPI - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/WebSubAPIRequest" - application/json: - schema: - $ref: "#/components/schemas/WebSubAPIRequest" - responses: - "201": - description: WebSubAPI created successfully - content: - application/json: - schema: - $ref: "#/components/schemas/WebSubAPI" - "400": - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - "409": - description: Conflict - WebSub 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 WebSubAPIs - description: List WebSubAPIs registered in the Gateway, optionally filtered by name, version, context, or status. - operationId: listWebSubAPIs - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: displayName - in: query - required: false - description: Filter by WebSub API display name - schema: - type: string - example: Weather WebSub API - - name: version - in: query - required: false - description: Filter by WebSub API version - schema: - type: string - example: v1.0 - - name: context - in: query - required: false - description: Filter by WebSub API context/path - schema: - type: string - example: /weather-events - - name: status - in: query - required: false - description: Filter by deployment status - schema: - type: string - enum: [ deployed, undeployed ] - example: undeployed - responses: - "200": - description: List of WebSubAPIs - content: - application/json: - schema: - type: object - properties: - status: - type: string - example: success - count: - type: integer - example: 5 - apis: - type: array - items: - $ref: "#/components/schemas/WebSubAPI" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - - /websub-apis/{id}/api-keys: - post: - summary: Create a new API key for a WebSub API - description: 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. - operationId: createWebSubAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API to generate the key for - schema: - type: string - example: weather-websub-api - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/APIKeyCreationRequest" - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationRequest" - responses: - '201': - description: API key created successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationResponse" - '400': - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '409': - description: Conflict (duplicate key or conflicting update) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebSub API not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - get: - summary: Get the list of API keys for a WebSub API - description: List all API keys for a WebSub API in the Gateway. - operationId: listWebSubAPIKeys - x-basicauth-roles: [admin, consumer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API to retrieve the keys for - schema: - type: string - example: weather-websub-api - responses: - '200': - description: List of API keys - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyListResponse" - '404': - description: WebSub API not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /websub-apis/{id}/api-keys/{apiKeyName}/regenerate: - post: - summary: Regenerate API key for a WebSub API - description: 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_`. - operationId: regenerateWebSubAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API - schema: - type: string - example: weather-websub-api - - name: apiKeyName - in: path - required: true - description: Name of the API key to regenerate - schema: - type: string - example: weather-api-key - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/APIKeyRegenerationRequest" - application/json: - schema: - $ref: "#/components/schemas/APIKeyRegenerationRequest" - responses: - '200': - description: API key rotated successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationResponse" - '400': - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebSub API or API key not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /websub-apis/{id}/api-keys/{apiKeyName}: - put: - summary: Update an API key for a WebSub API - description: Update an API key with a custom value instead of auto-generating one. - operationId: updateWebSubAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API - schema: - type: string - example: weather-websub-api - - name: apiKeyName - in: path - required: true - description: Name of the API key to update - schema: - type: string - example: weather-api-key - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/APIKeyUpdateRequest" - application/json: - schema: - $ref: "#/components/schemas/APIKeyUpdateRequest" - responses: - '200': - description: API key updated successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationResponse" - '400': - description: Invalid request (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '409': - description: Conflict (duplicate key or conflicting update) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebSub API or API key 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: Revoke an API key for a WebSub API - description: Revoke an API key. Once revoked, it can no longer be used to authenticate requests. - operationId: revokeWebSubAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API - schema: - type: string - example: weather-websub-api - - name: apiKeyName - in: path - required: true - description: Name of the API key to revoke - schema: - type: string - example: weather-api-key - responses: - '200': - description: API key revoked successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyRevocationResponse" - '400': - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebSub API or API key not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /websub-apis/{id}/secrets: - post: - summary: Generate a new HMAC secret for a WebSub API - description: | - Generates a cryptographically secure HMAC shared secret (format: `whsec_` + 64 hex chars) - for the given WebSub API. The plaintext value is returned **once** in the response and - never stored in plaintext. Use the secret as the `secret` parameter value in the - `websub-hmac-auth` policy to validate incoming webhook event signatures. - operationId: createWebSubAPISecret - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API - schema: - type: string - example: weather-websub-api - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookSecretCreationRequest" - application/yaml: - schema: - $ref: "#/components/schemas/WebhookSecretCreationRequest" - responses: - '201': - description: Secret generated successfully. The `secret` field will not appear in any future list or get response. - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookSecretCreationResponse" - '400': - description: Invalid request - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebSub API not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '409': - description: A secret with the same name already exists for this API - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - get: - summary: List HMAC secrets for a WebSub API - description: Returns metadata for all active HMAC secrets. Plaintext values are never included. - operationId: listWebSubAPISecrets - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API - schema: - type: string - example: weather-websub-api - responses: - '200': - description: List of secrets (no plaintext) - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookSecretListResponse" - '404': - description: WebSub API not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /websub-apis/{id}/secrets/{secretName}/regenerate: - post: - summary: Regenerate (rotate) a WebSub API HMAC secret - description: | - Replaces the secret value with a newly generated one. The old secret stops - validating signatures immediately. The new plaintext is returned once and - never stored unencrypted. - operationId: regenerateWebSubAPISecret - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API - schema: - type: string - example: weather-websub-api - - name: secretName - in: path - required: true - description: Name of the secret to regenerate - schema: - type: string - example: github-webhook - responses: - '200': - description: Secret rotated successfully. The new `secret` value will not appear again. - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookSecretCreationResponse" - '404': - description: WebSub API or secret not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /websub-apis/{id}/secrets/{secretName}: - delete: - summary: Delete a WebSub API HMAC secret - description: Permanently removes the secret. Existing webhook deliveries that used this secret will immediately fail signature validation. - operationId: deleteWebSubAPISecret - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebSub API - schema: - type: string - example: weather-websub-api - - name: secretName - in: path - required: true - description: Name of the secret to delete - schema: - type: string - example: github-webhook - responses: - '204': - description: Secret deleted successfully - '404': - description: WebSub API or secret not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /websub-apis/{id}: - get: - summary: Get WebSubAPI by id - description: Get a WebSubAPI by its ID. - operationId: getWebSubAPIById - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: | - Unique public identifier for the WebSub API. - schema: - type: string - example: weather-websub-api - responses: - "200": - description: WebSubAPI details - content: - application/json: - schema: - $ref: "#/components/schemas/WebSubAPI" - application/yaml: - schema: - $ref: "#/components/schemas/WebSubAPI" - "404": - description: WebSubAPI not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - - put: - summary: Update an existing WebSubAPI - description: Update an existing WebSubAPI in the Gateway. - operationId: updateWebSubAPI - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: | - Unique public identifier of the WebSub API to update. - schema: - type: string - example: weather-websub-api - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/WebSubAPIRequest" - application/json: - schema: - $ref: "#/components/schemas/WebSubAPIRequest" - responses: - "200": - description: WebSubAPI updated successfully - content: - application/json: - schema: - $ref: "#/components/schemas/WebSubAPI" - "400": - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - "404": - description: WebSubAPI 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 WebSubAPI - description: Delete a WebSubAPI from the Gateway. - operationId: deleteWebSubAPI - x-basicauth-roles: [admin, developer] - tags: - - WebSub API Management - parameters: - - name: id - in: path - required: true - description: | - Unique public identifier of the WebSub API to delete. - schema: - type: string - example: weather-websub-api - responses: - "200": - description: WebSubAPI deleted successfully - content: - application/json: - schema: - type: object - properties: - status: - type: string - example: success - message: - type: string - example: WebSubAPI deleted successfully - id: - type: string - example: weather-websub-api - "404": - description: WebSubAPI not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - "500": - description: Internal server error - content: - application/json: - 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" - - /webbroker-apis/{id}/api-keys: - post: - summary: Create a new API key for a WebBroker API - description: Generate a new API key for a WebBroker 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. - operationId: createWebBrokerAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebBroker API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebBroker API to generate the key for - schema: - type: string - example: stock-trading-webbroker-api - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/APIKeyCreationRequest" - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationRequest" - responses: - '201': - description: API key created successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationResponse" - '400': - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '409': - description: Conflict (duplicate key or conflicting update) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebBroker API not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - get: - summary: Get the list of API keys for a WebBroker API - description: List all API keys for a WebBroker API in the Gateway. - operationId: listWebBrokerAPIKeys - x-basicauth-roles: [admin, consumer] - tags: - - WebBroker API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebBroker API to retrieve the keys for - schema: - type: string - example: stock-trading-webbroker-api - responses: - '200': - description: List of API keys - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyListResponse" - '404': - description: WebBroker API not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate: - post: - summary: Regenerate API key for a WebBroker API - description: Regenerate an existing API key for a WebBroker API in the Gateway. The previous key is revoked and replaced with a new 32-byte random value encoded in hexadecimal, prefixed with `apip_`. - operationId: regenerateWebBrokerAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebBroker API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebBroker API - schema: - type: string - example: stock-trading-webbroker-api - - name: apiKeyName - in: path - required: true - description: Name of the API key to regenerate - schema: - type: string - example: trading-api-key - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/APIKeyRegenerationRequest" - application/json: - schema: - $ref: "#/components/schemas/APIKeyRegenerationRequest" - responses: - '200': - description: API key rotated successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationResponse" - '400': - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebBroker API or API key not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - - /webbroker-apis/{id}/api-keys/{apiKeyName}: - put: - summary: Update an API key for a WebBroker API - description: Update an API key with a custom value instead of auto-generating one. - operationId: updateWebBrokerAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebBroker API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebBroker API - schema: - type: string - example: stock-trading-webbroker-api - - name: apiKeyName - in: path - required: true - description: Name of the API key to update - schema: - type: string - example: trading-api-key - requestBody: - required: true - content: - application/yaml: - schema: - $ref: "#/components/schemas/APIKeyUpdateRequest" - application/json: - schema: - $ref: "#/components/schemas/APIKeyUpdateRequest" - responses: - '200': - description: API key updated successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyCreationResponse" - '400': - description: Invalid request (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '409': - description: Conflict (duplicate key or conflicting update) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebBroker API or API key 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: Revoke an API key for a WebBroker API - description: Revoke an API key. Once revoked, it can no longer be used to authenticate requests. - operationId: revokeWebBrokerAPIKey - x-basicauth-roles: [admin, consumer] - tags: - - WebBroker API Management - parameters: - - name: id - in: path - required: true - description: Unique public identifier of the WebBroker API - schema: - type: string - example: stock-trading-webbroker-api - - name: apiKeyName - in: path - required: true - description: Name of the API key to revoke - schema: - type: string - example: trading-api-key - responses: - '200': - description: API key revoked successfully - content: - application/json: - schema: - $ref: "#/components/schemas/APIKeyRevocationResponse" - '400': - description: Invalid configuration (validation failed) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: WebBroker API 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 @@ -4005,92 +2876,24 @@ components: value: development operations: - method: GET - path: /books - - method: POST - path: /books - - method: GET - path: /books/{id} - - method: PUT - path: /books/{id} - - method: DELETE - path: /books/{id} - status: - id: reading-list-api-v1.0 - state: deployed - createdAt: 2026-04-24T07:21:13Z - updatedAt: 2026-04-24T07:21:13Z - deployedAt: 2026-04-24T07:21:13Z - - WebSubAPIRequest: - type: object - required: - - apiVersion - - metadata - - kind - - spec - properties: - apiVersion: - type: string - description: API specification version - example: gateway.api-platform.wso2.com/v1 - enum: - - gateway.api-platform.wso2.com/v1 - kind: - type: string - description: API type - example: WebSubApi - enum: - - WebSubApi - metadata: - $ref: "#/components/schemas/Metadata" - spec: - $ref: '#/components/schemas/WebhookAPIData' - example: - apiVersion: gateway.api-platform.wso2.com/v1 - kind: WebSubApi - metadata: - name: github-events-v1.0 - spec: - displayName: GitHub Events - version: v1.0 - context: /github-events/$version - channels: - - name: issues - method: SUB - - name: pull_requests - method: SUB - - WebSubAPI: - allOf: - - $ref: '#/components/schemas/WebSubAPIRequest' - - 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/v1 - kind: WebSubApi - metadata: - name: github-events-v1.0 - spec: - displayName: GitHub Events - version: v1.0 - context: /github-events/$version - channels: - - name: issues - method: SUB - - name: pull_requests - method: SUB + path: /books + - method: POST + path: /books + - method: GET + path: /books/{id} + - method: PUT + path: /books/{id} + - method: DELETE + path: /books/{id} status: - id: github-events-v1.0 + id: reading-list-api-v1.0 state: deployed createdAt: 2026-04-24T07:21:13Z updatedAt: 2026-04-24T07:21:13Z deployedAt: 2026-04-24T07:21:13Z + + Metadata: type: object required: @@ -4437,128 +3240,6 @@ components: # ----------------------- # Webhook (Async) API schema # ----------------------- - WebhookAPIData: - type: object - required: - - displayName - - version - - context - 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: reading-list-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: /weather - 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 - allChannels: - $ref: '#/components/schemas/WebSubAllChannelPolicies' - channels: - type: object - description: Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel. - additionalProperties: - $ref: '#/components/schemas/WebSubChannel' - deploymentState: - type: string - description: 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. - enum: [deployed, undeployed] - default: deployed - example: deployed - - # WebSubChannel defines a single channel entry with its per-channel policies. - WebSubChannel: - type: object - description: A single channel definition with optional per-channel policy overrides. - properties: - on_subscription: - $ref: '#/components/schemas/WebSubEventPolicies' - on_unsubscription: - $ref: '#/components/schemas/WebSubEventPolicies' - on_message_received: - $ref: '#/components/schemas/WebSubEventPolicies' - on_message_delivery: - $ref: '#/components/schemas/WebSubEventPolicies' - - # WebSubEventPolicies defines policies for a single event type. - WebSubEventPolicies: - type: object - description: Policies for a single event type. - properties: - policies: - type: array - description: List of policies applied for this event type. - items: - $ref: '#/components/schemas/Policy' - - # WebSubAllChannelPolicies defines policies applied to all channels for each event type. - WebSubAllChannelPolicies: - type: object - description: Policies applied to all channels, organized by event type. - properties: - on_subscription: - $ref: '#/components/schemas/WebSubEventPolicies' - on_unsubscription: - $ref: '#/components/schemas/WebSubEventPolicies' - on_message_received: - $ref: '#/components/schemas/WebSubEventPolicies' - on_message_delivery: - $ref: '#/components/schemas/WebSubEventPolicies' - - # WebSubChannelPolicies defines per-channel policies organized by event type. - WebSubChannelPolicies: - type: object - description: Policies applied to a specific channel, organized by event type. - properties: - on_subscription: - type: array - description: Policies applied when a client subscribes to this channel (e.g., rbac) - items: - $ref: '#/components/schemas/Policy' - on_unsubscription: - type: array - description: Policies applied when a client unsubscribes from this channel - items: - $ref: '#/components/schemas/Policy' - on_message_received: - type: array - description: Policies applied when a message is received for this channel - items: - $ref: '#/components/schemas/Policy' - on_message_delivery: - type: array - description: Policies applied when delivering a message for this channel - items: - $ref: '#/components/schemas/Policy' - - # Simplified AsyncAPI Channel object represented as an OpenAPI schema. Channel: type: object description: Channel (topic/event stream) definition for async APIs. @@ -4625,290 +3306,15 @@ 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/v1 - enum: - - gateway.api-platform.wso2.com/v1 - 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/v1 - 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 - broker: - name: kafka-driver - type: kafka - properties: - brokers: - - 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 - on_connection_init: - policies: [] - on_produce: - policies: [] - on_consume: - policies: [] - - 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/v1 - 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 - broker: - name: kafka-driver - type: kafka - properties: - brokers: - - 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 - on_connection_init: - policies: [] - on_produce: - policies: [] - on_consume: - policies: [] - 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 - - broker - - 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' - 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) - 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 - WebBrokerApiBroker: - 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: - brokers: - - kafka-broker-1:9092 - - kafka-broker-2:9092 - WebBrokerApiAllChannelPolicies: - type: object - description: Protocol mediation policies applied to all channels - properties: - on_connection_init: - $ref: '#/components/schemas/WebBrokerApiPolicyGroup' - on_produce: - $ref: '#/components/schemas/WebBrokerApiPolicyGroup' - on_consume: - $ref: '#/components/schemas/WebBrokerApiPolicyGroup' - - WebBrokerApiPolicyGroup: - type: object - description: Group of policies - properties: - policies: - type: array - description: List of policies to apply - 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' - on_connection_init: - $ref: '#/components/schemas/WebBrokerApiPolicyGroup' - on_produce: - $ref: '#/components/schemas/WebBrokerApiPolicyGroup' - on_consume: - $ref: '#/components/schemas/WebBrokerApiPolicyGroup' - - 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 + + + @@ -6554,79 +4960,9 @@ components: type: string example: success - WebhookSecretCreationRequest: - type: object - required: - - displayName - properties: - displayName: - type: string - description: Human-readable label for this secret (used to derive the immutable name slug). - minLength: 1 - maxLength: 100 - example: GitHub Webhook - WebhookSecretInfo: - type: object - description: Metadata for an HMAC secret. The plaintext value is never included. - properties: - name: - type: string - description: URL-safe slug (immutable, used as path parameter for regenerate/delete). - example: github-webhook - displayName: - type: string - description: Human-readable label. - example: GitHub Webhook - status: - type: string - enum: [active, revoked] - example: active - createdAt: - type: string - format: date-time - example: "2026-06-01T10:00:00Z" - updatedAt: - type: string - format: date-time - example: "2026-06-01T10:00:00Z" - WebhookSecretCreationResponse: - type: object - properties: - status: - type: string - example: success - message: - type: string - example: Webhook secret generated successfully - secret: - type: string - description: | - The generated plaintext secret value (whsec_ prefix + 64 hex chars). - Returned exactly once — store it immediately as it will not be retrievable again. - example: "whsec_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" - webhookSecret: - $ref: '#/components/schemas/WebhookSecretInfo' - required: - - status - - message - - secret - WebhookSecretListResponse: - type: object - properties: - status: - type: string - example: success - totalCount: - type: integer - description: Total number of active secrets for this API - example: 2 - secrets: - type: array - items: - $ref: '#/components/schemas/WebhookSecretInfo' SecretListResponse: type: object diff --git a/gateway/gateway-controller/cmd/controller/main.go b/gateway/gateway-controller/cmd/controller/main.go index 8840bea24d..a5b2a8bed7 100644 --- a/gateway/gateway-controller/cmd/controller/main.go +++ b/gateway/gateway-controller/cmd/controller/main.go @@ -43,7 +43,6 @@ import ( "github.com/wso2/api-platform/gateway/gateway-controller/pkg/transform" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/version" - "github.com/wso2/api-platform/gateway/gateway-controller/pkg/webhooksecretxds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/xds" ) @@ -226,10 +225,6 @@ func main() { apiKeySnapshotManager := apikeyxds.NewAPIKeySnapshotManager(apiKeyStore, log) apiKeyXDSManager := apikeyxds.NewAPIKeyStateManager(apiKeyStore, apiKeySnapshotManager, log) - // Initialize in-memory webhook secret store (shared with the HMAC policy via the common package) - webhookSecretStore := webhooksecret.GetStoreInstance() - webhookSecretSnapshotManager := webhooksecretxds.NewSnapshotManager(webhookSecretStore, log) - // Initialize in-memory lazy resource store and components for xDS lazyResourceStore := storage.NewLazyResourceStore(log) lazyResourceSnapshotManager := lazyresourcexds.NewLazyResourceSnapshotManager(lazyResourceStore, log) @@ -298,17 +293,6 @@ func main() { } // Create secrets service secretsService = secrets.NewSecretsService(db, encryptionProviderManager, log) - - // Load webhook secrets from database into the in-memory store - log.Info("Loading webhook secrets from database") - if err := storage.LoadWebhookSecretsFromDatabase(db, encryptionProviderManager, webhookSecretStore); err != nil { - log.Error("Failed to load webhook secrets from database", slog.Any("error", err)) - os.Exit(1) - } - log.Info("Loaded webhook secrets from database") - if err := webhookSecretSnapshotManager.RefreshSnapshot(); err != nil { - log.Warn("Failed to generate initial webhook secret xDS snapshot", slog.Any("error", err)) - } } log.Info("Loaded encryption providers") @@ -496,7 +480,7 @@ func main() { cfg.Controller.PolicyServer.TLS.KeyFile, )) } - policyXDSServer := policyxds.NewServer(policySnapshotManager, apiKeySnapshotManager, lazyResourceSnapshotManager, subscriptionSnapshotManager, webhookSecretSnapshotManager, cfg.Controller.PolicyServer.Port, log, serverOpts...) + policyXDSServer := policyxds.NewServer(policySnapshotManager, apiKeySnapshotManager, lazyResourceSnapshotManager, subscriptionSnapshotManager, nil, cfg.Controller.PolicyServer.Port, log, serverOpts...) go func() { if err := policyXDSServer.Start(); err != nil { log.Error("Policy xDS server failed", slog.Any("error", err)) @@ -522,7 +506,6 @@ func main() { apiSvc := utils.NewAPIDeploymentService(configStore, db, snapshotManager, validator, &cfg.Router, eventHubInstance, gatewayID, secretsService) mcpSvc := utils.NewMCPDeploymentService(configStore, db, snapshotManager, policyManager, policyValidator, eventHubInstance, gatewayID, secretsService) - webhookSecretService := utils.NewWebhookSecretService(db, encryptionProviderManager, webhookSecretStore, eventHubInstance, gatewayID, log) llmSvc := utils.NewLLMDeploymentService(configStore, db, snapshotManager, lazyResourceXDSManager, templateDefinitions, apiSvc, &cfg.Router, policyVersionResolver, policyValidator) @@ -542,8 +525,8 @@ func main() { subscriptionSnapshotManager, eventHubInstance, secretsService, - webhookSecretStore, - webhookSecretSnapshotManager, + webhooksecret.GetStoreInstance(), + nil, ) if err := cpClient.Start(); err != nil { log.Error("Failed to start control plane client", slog.Any("error", err)) @@ -598,9 +581,6 @@ func main() { cfg, policyDefinitions, secretsService, - webhookSecretStore, - webhookSecretSnapshotManager, - encryptionProviderManager, ) if err := evtListener.Start(); err != nil { log.Error("Failed to start event listener", slog.Any("error", err)) @@ -626,7 +606,6 @@ func main() { subscriptionSnapshotManager, secretsService, restAPIService, - webhookSecretService, ) // Load immutable gateway artifacts from the filesystem (no-op when immutable mode is disabled). diff --git a/gateway/gateway-controller/go.mod b/gateway/gateway-controller/go.mod index 502a8d6bbd..11d8f790d8 100644 --- a/gateway/gateway-controller/go.mod +++ b/gateway/gateway-controller/go.mod @@ -8,7 +8,7 @@ require ( github.com/getkin/kin-openapi v0.133.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 @@ -18,7 +18,7 @@ require ( github.com/knadh/koanf/v2 v2.3.2 github.com/mattn/go-sqlite3 v1.14.41 github.com/microsoft/go-mssqldb v1.10.0 - github.com/oapi-codegen/runtime v1.1.2 + github.com/oapi-codegen/runtime v1.5.0 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.11.1 @@ -44,8 +44,8 @@ require ( github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect @@ -54,8 +54,9 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.18.6 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -67,20 +68,23 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect 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.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.50.0 // 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.3 // indirect + golang.org/x/crypto v0.51.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.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 + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.14.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 ) replace github.com/wso2/api-platform/common => ../../common diff --git a/gateway/gateway-controller/go.sum b/gateway/gateway-controller/go.sum index 00ef3bd0dc..8e2367eb80 100644 --- a/gateway/gateway-controller/go.sum +++ b/gateway/gateway-controller/go.sum @@ -47,10 +47,10 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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-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/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-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= @@ -69,8 +69,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.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= @@ -84,8 +84,8 @@ github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT 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/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -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/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A= @@ -106,8 +106,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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.41/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= @@ -121,8 +121,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= -github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= +github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= +github.com/oapi-codegen/runtime v1.5.0 h1:aiil4QnH+eiWYSO60eaYZ4aur7sJH3rz6BvT5EBFnxc= +github.com/oapi-codegen/runtime v1.5.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= @@ -144,8 +146,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= 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/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -156,8 +158,8 @@ 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= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= github.com/wso2/api-platform/sdk/core v0.2.18 h1:j6UhHjGBa9qCxqbT4p8cwLUKkQVtpvoMTMsjs1DfYR0= @@ -170,38 +172,38 @@ 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.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= 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/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -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/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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/handlers/handlerkit/handlerkit.go b/gateway/gateway-controller/pkg/api/handlers/handlerkit/handlerkit.go new file mode 100644 index 0000000000..450681ee89 --- /dev/null +++ b/gateway/gateway-controller/pkg/api/handlers/handlerkit/handlerkit.go @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2025, 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 handlerkit holds generic request-handling infrastructure shared by +// every gateway-controller API handler kind (REST, LLM, MCP, subscriptions, +// api-keys, and — in an event-gateway-controller binary — WebSub/WebBroker). +// It exists so that handler code living outside this module (an +// event-gateway-controller binary importing gateway-controller as a library) +// can reuse the same request-body binding, auth-context extraction, and +// control-plane push behavior as core, without duplicating it. +package handlerkit + +import ( + "encoding/json" + "io" + "log/slog" + "net/http" + "strings" + "time" + + "github.com/wso2/api-platform/common/authenticators" + "github.com/wso2/api-platform/common/eventhub" + commonmodels "github.com/wso2/api-platform/common/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/constants" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/controlplane" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" + "github.com/wso2/go-httpkit/httputil" + "gopkg.in/yaml.v3" +) + +// errorResponse mirrors the wire shape of the generated api.ErrorResponse +// (status/message fields) without depending on any specific generated +// package's type, so this helper can be reused by handler code built against +// a different generated ServerInterface (e.g. an event-gateway-controller's +// own eventgateway.ErrorResponse). +type errorResponse struct { + Status string `json:"status"` + Message string `json:"message"` +} + +// BindRequestBody binds the request body based on the Content-Type header. +// Supports both JSON and YAML content types. Handles Content-Type headers +// case-insensitively and strips parameters (e.g., charset). +func BindRequestBody(r *http.Request, request interface{}) error { + contentType := r.Header.Get("Content-Type") + contentType = strings.TrimSpace(contentType) + if idx := strings.Index(contentType, ";"); idx != -1 { + contentType = contentType[:idx] + } + contentType = strings.TrimSpace(strings.ToLower(contentType)) + + body, err := io.ReadAll(r.Body) + if err != nil { + return err + } + + if contentType == "application/yaml" || contentType == "text/yaml" { + return yaml.Unmarshal(body, request) + } + return json.Unmarshal(body, request) +} + +// ExtractAuthenticatedUser extracts and validates the authenticated user from +// the request context. Returns the AuthContext object and handles error +// responses automatically. +func ExtractAuthenticatedUser(w http.ResponseWriter, r *http.Request, logger *slog.Logger, operationName string, correlationID string) (*commonmodels.AuthContext, bool) { + user, ok := authenticators.GetAuthContext(r) + if !ok { + logger.Error("Authentication context not found", + slog.String("operation", operationName), + slog.String("correlation_id", correlationID)) + httputil.WriteJSON(w, http.StatusUnauthorized, errorResponse{ + Status: "error", + Message: "Authentication context not available", + }) + return nil, false + } + logger.Debug("Authenticated user extracted", + slog.String("operation", operationName), + slog.String("user_id", user.UserID), + slog.Any("roles", user.Roles), + slog.String("correlation_id", correlationID)) + return &user, true +} + +// DeploymentPusher wraps the DP->CP (data-plane to control-plane) artifact +// push behavior shared by every artifact kind's create/update/delete flow. +type DeploymentPusher struct { + Store *storage.ConfigStore + ControlPlaneClient controlplane.ControlPlaneClient + SystemConfig *config.Config +} + +// WaitForDeploymentAndPush waits for API deployment to complete and pushes it +// to the control plane. This is only relevant for artifacts created directly +// via a gateway endpoint (not from platform API). +// +// minDeployedAt is the DeployedAt of the deployment this push was triggered for. +func (p *DeploymentPusher) WaitForDeploymentAndPush(configID string, correlationID string, minDeployedAt *time.Time, log *slog.Logger) { + if correlationID != "" { + log = log.With(slog.String("correlation_id", correlationID)) + } + + timeout := time.NewTimer(constants.CPPushDeploymentTimeout) + ticker := time.NewTicker(constants.CPPushPollInterval) + defer timeout.Stop() + defer ticker.Stop() + + for { + select { + case <-timeout.C: + log.Warn("Timeout waiting for API deployment to complete before pushing to control plane", + slog.String("config_id", configID)) + return + + case <-ticker.C: + cfg, err := p.Store.Get(configID) + if err != nil { + log.Warn("Config not found while waiting for deployment completion", + slog.String("config_id", configID)) + continue + } + + // Not deployed yet, or the store still holds a snapshot older than the + // deployment we were triggered for — keep waiting. + if cfg.DeployedAt == nil || (minDeployedAt != nil && cfg.DeployedAt.Before(*minDeployedAt)) { + continue + } + + log.Info("API deployed successfully, pushing to control plane", + slog.String("config_id", configID), + slog.String("displayName", cfg.DisplayName)) + + apiID := configID + deploymentID := cfg.DeploymentID + + if err := p.ControlPlaneClient.PushArtifact(apiID, cfg, deploymentID); err != nil { + log.Error("Failed to push deployment to control plane", + slog.String("api_id", apiID), + slog.Any("error", err)) + } else { + log.Info("Successfully pushed deployment to control plane", + slog.String("api_id", apiID)) + } + return + } + } +} + +// PushArtifactUndeploy pushes an undeploy notification for a deleted artifact +// to the control plane, if this gateway is connected and deployment sync is +// enabled. It only applies to gateway-originated artifacts. +func (p *DeploymentPusher) PushArtifactUndeploy(cfg *models.StoredConfig, log *slog.Logger) { + if cfg == nil || cfg.Origin != models.OriginGatewayAPI { + return + } + if p.ControlPlaneClient != nil && p.ControlPlaneClient.IsConnected() && p.SystemConfig.Controller.ControlPlane.DeploymentSyncEnabled { + undeploy := *cfg + undeploy.DesiredState = models.StateUndeployed + go func(uc models.StoredConfig) { + if err := p.ControlPlaneClient.PushArtifact(uc.UUID, &uc, uc.DeploymentID); err != nil { + log.Error("Failed to push artifact undeploy to control plane", + slog.String("artifact_id", uc.UUID), slog.Any("error", err)) + } + }(undeploy) + } +} + +// EventPublisher wraps event-hub publication for artifact lifecycle changes. +type EventPublisher struct { + EventHub eventhub.EventHub + GatewayID string +} + +// PublishEvent publishes a lifecycle event to the event hub so all replicas +// (including self) converge through the event listener sync path. +func (p *EventPublisher) PublishEvent(eventType eventhub.EventType, action, entityID, correlationID string, logger *slog.Logger) { + event := eventhub.Event{ + GatewayID: p.GatewayID, + OriginatedTimestamp: eventTimestamp(), + EventType: eventType, + Action: action, + EntityID: entityID, + EventID: correlationID, + EventData: eventhub.EmptyEventData, + } + + if err := p.EventHub.PublishEvent(p.GatewayID, event); err != nil { + logger.Warn("Failed to publish event to event hub", + slog.String("gateway_id", p.GatewayID), + slog.String("event_type", string(eventType)), + slog.String("action", action), + slog.String("entity_id", entityID), + slog.Any("error", err)) + } +} + +// eventTimestamp is a seam so tests could stub time if ever needed; today it +// simply returns the current time. +func eventTimestamp() time.Time { + return time.Now() +} diff --git a/gateway/gateway-controller/pkg/api/handlers/handlers.go b/gateway/gateway-controller/pkg/api/handlers/handlers.go index 7ed21bcad4..cd139e3e23 100644 --- a/gateway/gateway-controller/pkg/api/handlers/handlers.go +++ b/gateway/gateway-controller/pkg/api/handlers/handlers.go @@ -21,7 +21,6 @@ package handlers import ( "encoding/json" "fmt" - "io" "sort" "strconv" "strings" @@ -31,16 +30,15 @@ import ( "log/slog" "net/http" - "github.com/wso2/api-platform/common/authenticators" "github.com/wso2/api-platform/common/eventhub" commonmodels "github.com/wso2/api-platform/common/models" "github.com/wso2/api-platform/common/redact" adminapi "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/admin" api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/handlers/handlerkit" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/middleware" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/apikeyxds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" - "github.com/wso2/api-platform/gateway/gateway-controller/pkg/constants" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/controlplane" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/lazyresourcexds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" @@ -51,7 +49,6 @@ import ( "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/xds" "github.com/wso2/go-httpkit/httputil" - "gopkg.in/yaml.v3" ) // APIServer implements the generated ServerInterface @@ -82,7 +79,6 @@ type APIServer struct { gatewayID string subscriptionSnapshotUpdater utils.SubscriptionSnapshotUpdater subscriptionResourceService *utils.SubscriptionResourceService - webhookSecretService *utils.WebhookSecretService } // NewAPIServer creates a new API server with dependencies @@ -103,7 +99,6 @@ func NewAPIServer( subscriptionSnapshotUpdater utils.SubscriptionSnapshotUpdater, secretService *secrets.SecretService, restAPIService *restapi.RestAPIService, - webhookSecretService *utils.WebhookSecretService, ) *APIServer { if db == nil { panic("APIServer requires non-nil storage") @@ -154,7 +149,6 @@ func NewAPIServer( gatewayID: gatewayID, subscriptionSnapshotUpdater: subscriptionSnapshotUpdater, subscriptionResourceService: subscriptionResourceService, - webhookSecretService: webhookSecretService, } // Wire the DP->CP push into the LLM/MCP deployment services so create flows push to the // control plane from the service layer (mirroring the REST API service), instead of the @@ -309,7 +303,7 @@ func (s *APIServer) SearchDeployments(w http.ResponseWriter, r *http.Request, ki switch kind { case string(api.MCPProxyConfigurationKindMcp): envelopeKey = "mcpProxies" - case string(api.WebSubAPIKindWebSubApi): + case "WebSubApi": envelopeKey = "websubApis" } @@ -345,99 +339,33 @@ func (s *APIServer) GetAPIByNameVersion(w http.ResponseWriter, r *http.Request, // has been deleted from this gateway. The control plane keeps the artifact but marks // it undeployed (it is not removed and can be re-deployed later). It is a no-op for // control-plane-originated artifacts or when push is disabled / disconnected. -func (s *APIServer) pushArtifactUndeploy(cfg *models.StoredConfig, log *slog.Logger) { - if cfg == nil || cfg.Origin != models.OriginGatewayAPI { - return - } - if s.controlPlaneClient != nil && s.controlPlaneClient.IsConnected() && s.systemConfig.Controller.ControlPlane.DeploymentSyncEnabled { - undeploy := *cfg - undeploy.DesiredState = models.StateUndeployed - go func(uc models.StoredConfig) { - if err := s.controlPlaneClient.PushArtifact(uc.UUID, &uc, uc.DeploymentID); err != nil { - log.Error("Failed to push artifact undeploy to control plane", - slog.String("artifact_id", uc.UUID), slog.Any("error", err)) - } - }(undeploy) +// deploymentPusher builds the handlerkit.DeploymentPusher for this server's +// current dependencies. pushArtifactUndeploy/waitForDeploymentAndPush delegate +// to it so the shared push logic lives in one place (handlerkit), reusable by +// any binary that imports gateway-controller as a library. +func (s *APIServer) deploymentPusher() *handlerkit.DeploymentPusher { + return &handlerkit.DeploymentPusher{ + Store: s.store, + ControlPlaneClient: s.controlPlaneClient, + SystemConfig: s.systemConfig, } } +func (s *APIServer) pushArtifactUndeploy(cfg *models.StoredConfig, log *slog.Logger) { + s.deploymentPusher().PushArtifactUndeploy(cfg, log) +} + // waitForDeploymentAndPush waits for API deployment to complete and pushes it to the control plane // This is only called for APIs created directly via gateway endpoint (not from platform API). // // minDeployedAt is the DeployedAt of the deployment this push was triggered for. func (s *APIServer) waitForDeploymentAndPush(configID string, correlationID string, minDeployedAt *time.Time, log *slog.Logger) { - // Create a logger with correlation ID if provided - if correlationID != "" { - log = log.With(slog.String("correlation_id", correlationID)) - } - - // Poll for deployment status with timeout - timeout := time.NewTimer(constants.CPPushDeploymentTimeout) - ticker := time.NewTicker(constants.CPPushPollInterval) - defer timeout.Stop() - defer ticker.Stop() - - for { - select { - case <-timeout.C: - log.Warn("Timeout waiting for API deployment to complete before pushing to control plane", - slog.String("config_id", configID)) - return - - case <-ticker.C: - cfg, err := s.store.Get(configID) - if err != nil { - log.Warn("Config not found while waiting for deployment completion", - slog.String("config_id", configID)) - continue - } - - // Not deployed yet, or the store still holds a snapshot older than the - // deployment we were triggered for — keep waiting. - if cfg.DeployedAt == nil || (minDeployedAt != nil && cfg.DeployedAt.Before(*minDeployedAt)) { - continue - } - - log.Info("API deployed successfully, pushing to control plane", - slog.String("config_id", configID), - slog.String("displayName", cfg.DisplayName)) - - apiID := configID - deploymentID := cfg.DeploymentID - - if err := s.controlPlaneClient.PushArtifact(apiID, cfg, deploymentID); err != nil { - log.Error("Failed to push deployment to control plane", - slog.String("api_id", apiID), - slog.Any("error", err)) - } else { - log.Info("Successfully pushed deployment to control plane", - slog.String("api_id", apiID)) - } - return - } - } + s.deploymentPusher().WaitForDeploymentAndPush(configID, correlationID, minDeployedAt, log) } // publishWebSubEvent publishes an event for WebSub API lifecycle changes. func (s *APIServer) publishWebSubEvent(eventType eventhub.EventType, action, entityID, correlationID string, logger *slog.Logger) { - event := eventhub.Event{ - GatewayID: s.gatewayID, - OriginatedTimestamp: time.Now(), - EventType: eventType, - Action: action, - EntityID: entityID, - EventID: correlationID, - EventData: eventhub.EmptyEventData, - } - - if err := s.eventHub.PublishEvent(s.gatewayID, event); err != nil { - logger.Warn("Failed to publish event to event hub", - slog.String("gateway_id", s.gatewayID), - slog.String("event_type", string(eventType)), - slog.String("action", action), - slog.String("entity_id", entityID), - slog.Any("error", err)) - } + (&handlerkit.EventPublisher{EventHub: s.eventHub, GatewayID: s.gatewayID}).PublishEvent(eventType, action, entityID, correlationID, logger) } // GetConfigDump implements the GET /config_dump endpoint @@ -620,46 +548,14 @@ func ptr[T any](v T) *T { return &v } // extractAuthenticatedUser extracts and validates the authenticated user from the request context. // Returns the AuthContext object and handles error responses automatically. func (s *APIServer) extractAuthenticatedUser(w http.ResponseWriter, r *http.Request, operationName string, correlationID string) (*commonmodels.AuthContext, bool) { - log := s.logger - user, ok := authenticators.GetAuthContext(r) - if !ok { - log.Error("Authentication context not found", - slog.String("operation", operationName), - slog.String("correlation_id", correlationID)) - httputil.WriteJSON(w, http.StatusUnauthorized, api.ErrorResponse{ - Status: "error", - Message: "Authentication context not available", - }) - return nil, false - } - log.Debug("Authenticated user extracted", - slog.String("operation", operationName), - slog.String("user_id", user.UserID), - slog.Any("roles", user.Roles), - slog.String("correlation_id", correlationID)) - return &user, true + return handlerkit.ExtractAuthenticatedUser(w, r, s.logger, operationName, correlationID) } // bindRequestBody binds the request body based on Content-Type header. // Supports both JSON and YAML content types. // Handles Content-Type headers case-insensitively and strips parameters (e.g., charset). func (s *APIServer) bindRequestBody(r *http.Request, request interface{}) error { - contentType := r.Header.Get("Content-Type") - contentType = strings.TrimSpace(contentType) - if idx := strings.Index(contentType, ";"); idx != -1 { - contentType = contentType[:idx] - } - contentType = strings.TrimSpace(strings.ToLower(contentType)) - - body, err := io.ReadAll(r.Body) - if err != nil { - return err - } - - if contentType == "application/yaml" || contentType == "text/yaml" { - return yaml.Unmarshal(body, request) - } - return json.Unmarshal(body, request) + return handlerkit.BindRequestBody(r, request) } // getLLMProviderTemplate extracts the template name from sourceConfig and retrieves the template. diff --git a/gateway/gateway-controller/pkg/api/handlers/handlers_test.go b/gateway/gateway-controller/pkg/api/handlers/handlers_test.go index ba763b0326..f9bf138e43 100644 --- a/gateway/gateway-controller/pkg/api/handlers/handlers_test.go +++ b/gateway/gateway-controller/pkg/api/handlers/handlers_test.go @@ -1072,9 +1072,6 @@ func createTestAPIServerWithDB(db storage.Storage) *APIServer { routerCfg := &config.RouterConfig{ GatewayHost: "localhost", VHosts: *vhosts, - EventGateway: config.EventGatewayConfig{ - TimeoutSeconds: 10, - }, } httpClient := &http.Client{Timeout: 10 * time.Second} systemCfg := &config.Config{ @@ -3484,27 +3481,6 @@ func BenchmarkListRestAPIs(b *testing.B) { } // Test for WebSubApi kind in buildStoredPolicyFromAPI -func TestBuildStoredPolicyFromAPIWebSubApi(t *testing.T) { - server := createTestAPIServer() - - // Note: WebSubApi requires different data structure than RestApi - // The function will return nil if parsing fails - apiConfig := api.WebSubAPI{ - Kind: api.WebSubAPIKindWebSubApi, - } - cfg := &models.StoredConfig{ - UUID: "0000-test-id-0000-000000000000", - Kind: string(api.WebSubAPIKindWebSubApi), - Configuration: apiConfig, - SourceConfiguration: apiConfig, - Origin: models.OriginGatewayAPI, - } - - result := policybuilder.DerivePolicyFromAPIConfig(cfg, server.routerConfig, server.systemConfig, server.policyDefinitions) - // Should return nil because the spec can't be parsed as WebhookAPIData without proper setup - assert.Nil(t, result) -} - // Test GetConfigDump with config missing handle func TestGetConfigDumpMissingHandle(t *testing.T) { server := createTestAPIServer() @@ -3738,27 +3714,6 @@ func TestAPIKeyServiceNotConfigured(t *testing.T) { } // Test for WebSubAPI - simplified test -func TestBuildStoredPolicyFromAPIWebSubApiWithPolicies(t *testing.T) { - server := createTestAPIServer() - - // WebSubApi requires specific data structure that's complex to mock - // Testing that the function handles WebSubApi kind without panicking - apiConfig := api.WebSubAPI{ - Kind: api.WebSubAPIKindWebSubApi, - } - cfg := &models.StoredConfig{ - UUID: "0000-test-id-0000-000000000000", - Kind: string(api.WebSubAPIKindWebSubApi), - Configuration: apiConfig, - SourceConfiguration: apiConfig, - Origin: models.OriginGatewayAPI, - } - - result := policybuilder.DerivePolicyFromAPIConfig(cfg, server.routerConfig, server.systemConfig, server.policyDefinitions) - // Should return nil since we don't have valid spec data - assert.Nil(t, result) -} - // Test ListMCPProxies with stored configs that have unmarshal issues func TestListMCPProxiesUnmarshalError(t *testing.T) { server := createTestAPIServer() diff --git a/gateway/gateway-controller/pkg/api/handlers/resource_response.go b/gateway/gateway-controller/pkg/api/handlers/resource_response.go index 93ccb623ff..ccb8999484 100644 --- a/gateway/gateway-controller/pkg/api/handlers/resource_response.go +++ b/gateway/gateway-controller/pkg/api/handlers/resource_response.go @@ -68,26 +68,6 @@ func buildResourceResponse(cfg any, status api.ResourceStatus) any { cp := *v cp.Status = &status return cp - case api.WebSubAPI: - v.Status = &status - return v - case *api.WebSubAPI: - if v == nil { - return nil - } - cp := *v - cp.Status = &status - return cp - case api.WebBrokerApi: - v.Status = &status - return v - case *api.WebBrokerApi: - if v == nil { - return nil - } - cp := *v - cp.Status = &status - return cp case api.MCPProxyConfiguration: v.Status = &status return v diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index dc154f88c0..778d294b8a 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -40,9 +40,9 @@ const ( // Defines values for APIKeyStatus. const ( - APIKeyStatusActive APIKeyStatus = "active" - APIKeyStatusExpired APIKeyStatus = "expired" - APIKeyStatusRevoked APIKeyStatus = "revoked" + Active APIKeyStatus = "active" + Expired APIKeyStatus = "expired" + Revoked APIKeyStatus = "revoked" ) // Defines values for APIKeyCreationRequestExpiresInUnit. @@ -398,64 +398,6 @@ const ( UpstreamAuthAuthTypeApiKey UpstreamAuthAuthType = "api-key" ) -// Defines values for WebBrokerApiApiVersion. -const ( - WebBrokerApiApiVersionGatewayApiPlatformWso2Comv1 WebBrokerApiApiVersion = "gateway.api-platform.wso2.com/v1" -) - -// Defines values for WebBrokerApiKind. -const ( - WebBrokerApiKindWebBrokerApi WebBrokerApiKind = "WebBrokerApi" -) - -// Defines values for WebBrokerApiDataDeploymentState. -const ( - WebBrokerApiDataDeploymentStateDeployed WebBrokerApiDataDeploymentState = "deployed" - WebBrokerApiDataDeploymentStateUndeployed WebBrokerApiDataDeploymentState = "undeployed" -) - -// Defines values for WebBrokerApiRequestApiVersion. -const ( - WebBrokerApiRequestApiVersionGatewayApiPlatformWso2Comv1 WebBrokerApiRequestApiVersion = "gateway.api-platform.wso2.com/v1" -) - -// Defines values for WebBrokerApiRequestKind. -const ( - WebBrokerApiRequestKindWebBrokerApi WebBrokerApiRequestKind = "WebBrokerApi" -) - -// Defines values for WebSubAPIApiVersion. -const ( - WebSubAPIApiVersionGatewayApiPlatformWso2Comv1 WebSubAPIApiVersion = "gateway.api-platform.wso2.com/v1" -) - -// Defines values for WebSubAPIKind. -const ( - WebSubAPIKindWebSubApi WebSubAPIKind = "WebSubApi" -) - -// Defines values for WebSubAPIRequestApiVersion. -const ( - WebSubAPIRequestApiVersionGatewayApiPlatformWso2Comv1 WebSubAPIRequestApiVersion = "gateway.api-platform.wso2.com/v1" -) - -// Defines values for WebSubAPIRequestKind. -const ( - WebSubAPIRequestKindWebSubApi WebSubAPIRequestKind = "WebSubApi" -) - -// Defines values for WebhookAPIDataDeploymentState. -const ( - WebhookAPIDataDeploymentStateDeployed WebhookAPIDataDeploymentState = "deployed" - WebhookAPIDataDeploymentStateUndeployed WebhookAPIDataDeploymentState = "undeployed" -) - -// Defines values for WebhookSecretInfoStatus. -const ( - WebhookSecretInfoStatusActive WebhookSecretInfoStatus = "active" - WebhookSecretInfoStatusRevoked WebhookSecretInfoStatus = "revoked" -) - // Defines values for ListLLMProvidersParamsStatus. const ( ListLLMProvidersParamsStatusDeployed ListLLMProvidersParamsStatus = "deployed" @@ -476,8 +418,8 @@ const ( // Defines values for ListRestAPIsParamsStatus. const ( - ListRestAPIsParamsStatusDeployed ListRestAPIsParamsStatus = "deployed" - ListRestAPIsParamsStatusUndeployed ListRestAPIsParamsStatus = "undeployed" + Deployed ListRestAPIsParamsStatus = "deployed" + Undeployed ListRestAPIsParamsStatus = "undeployed" ) // Defines values for ListSubscriptionsParamsStatus. @@ -487,18 +429,6 @@ const ( REVOKED ListSubscriptionsParamsStatus = "REVOKED" ) -// Defines values for ListWebBrokerApisParamsStatus. -const ( - ListWebBrokerApisParamsStatusDeployed ListWebBrokerApisParamsStatus = "deployed" - ListWebBrokerApisParamsStatusUndeployed ListWebBrokerApisParamsStatus = "undeployed" -) - -// Defines values for ListWebSubAPIsParamsStatus. -const ( - Deployed ListWebSubAPIsParamsStatus = "deployed" - Undeployed ListWebSubAPIsParamsStatus = "undeployed" -) - // APIConfigData defines model for APIConfigData. type APIConfigData struct { // Context Base path for all API routes (must start with /, no trailing slash). Use $version to embed the version in the path (e.g., /reading-list/$version resolves to /reading-list/v1.0). @@ -1759,304 +1689,6 @@ 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 - -// 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"` - - // 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"` - - // 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"` - - // 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 -type WebBrokerApiConsumeConfig struct { - // Topic Kafka topic to consume messages from - Topic string `json:"topic" yaml:"topic"` -} - -// WebBrokerApiData defines model for WebBrokerApiData. -type WebBrokerApiData struct { - // 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"` - - // 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"` - - // 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 - -// 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 -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 - ApiVersion WebSubAPIApiVersion `json:"apiVersion" yaml:"apiVersion"` - - // Kind API type - Kind WebSubAPIKind `json:"kind" yaml:"kind"` - Metadata Metadata `json:"metadata" yaml:"metadata"` - Spec WebhookAPIData `json:"spec" yaml:"spec"` - - // Status Server-managed lifecycle fields. Populated on responses. - Status *ResourceStatus `json:"status,omitempty" yaml:"status,omitempty"` -} - -// WebSubAPIApiVersion API specification version -type WebSubAPIApiVersion string - -// WebSubAPIKind API type -type WebSubAPIKind string - -// WebSubAPIRequest defines model for WebSubAPIRequest. -type WebSubAPIRequest struct { - // ApiVersion API specification version - ApiVersion WebSubAPIRequestApiVersion `json:"apiVersion" yaml:"apiVersion"` - - // Kind API type - Kind WebSubAPIRequestKind `json:"kind" yaml:"kind"` - Metadata Metadata `json:"metadata" yaml:"metadata"` - Spec WebhookAPIData `json:"spec" yaml:"spec"` -} - -// WebSubAPIRequestApiVersion API specification version -type WebSubAPIRequestApiVersion string - -// WebSubAPIRequestKind API type -type WebSubAPIRequestKind string - -// WebSubAllChannelPolicies Policies applied to all channels, organized by event type. -type WebSubAllChannelPolicies struct { - // OnMessageDelivery Policies for a single event type. - OnMessageDelivery *WebSubEventPolicies `json:"on_message_delivery,omitempty" yaml:"on_message_delivery,omitempty"` - - // OnMessageReceived Policies for a single event type. - OnMessageReceived *WebSubEventPolicies `json:"on_message_received,omitempty" yaml:"on_message_received,omitempty"` - - // OnSubscription Policies for a single event type. - OnSubscription *WebSubEventPolicies `json:"on_subscription,omitempty" yaml:"on_subscription,omitempty"` - - // OnUnsubscription Policies for a single event type. - OnUnsubscription *WebSubEventPolicies `json:"on_unsubscription,omitempty" yaml:"on_unsubscription,omitempty"` -} - -// WebSubChannel A single channel definition with optional per-channel policy overrides. -type WebSubChannel struct { - // OnMessageDelivery Policies for a single event type. - OnMessageDelivery *WebSubEventPolicies `json:"on_message_delivery,omitempty" yaml:"on_message_delivery,omitempty"` - - // OnMessageReceived Policies for a single event type. - OnMessageReceived *WebSubEventPolicies `json:"on_message_received,omitempty" yaml:"on_message_received,omitempty"` - - // OnSubscription Policies for a single event type. - OnSubscription *WebSubEventPolicies `json:"on_subscription,omitempty" yaml:"on_subscription,omitempty"` - - // OnUnsubscription Policies for a single event type. - OnUnsubscription *WebSubEventPolicies `json:"on_unsubscription,omitempty" yaml:"on_unsubscription,omitempty"` -} - -// WebSubEventPolicies Policies for a single event type. -type WebSubEventPolicies struct { - // Policies List of policies applied for this event type. - Policies *[]Policy `json:"policies,omitempty" yaml:"policies,omitempty"` -} - -// WebhookAPIData defines model for WebhookAPIData. -type WebhookAPIData struct { - // AllChannels Policies applied to all channels, organized by event type. - AllChannels *WebSubAllChannelPolicies `json:"allChannels,omitempty" yaml:"allChannels,omitempty"` - - // Channels Per-channel configuration keyed by channel name. Each key is a channel name and defines policies applied only to that channel. - Channels *map[string]WebSubChannel `json:"channels,omitempty" yaml:"channels,omitempty"` - - // 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 *WebhookAPIDataDeploymentState `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"` - - // 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"` -} - -// WebhookAPIDataDeploymentState 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. -type WebhookAPIDataDeploymentState string - -// WebhookSecretCreationRequest defines model for WebhookSecretCreationRequest. -type WebhookSecretCreationRequest struct { - // DisplayName Human-readable label for this secret (used to derive the immutable name slug). - DisplayName string `json:"displayName" yaml:"displayName"` -} - -// WebhookSecretCreationResponse defines model for WebhookSecretCreationResponse. -type WebhookSecretCreationResponse struct { - Message string `json:"message" yaml:"message"` - - // Secret The generated plaintext secret value (whsec_ prefix + 64 hex chars). - // Returned exactly once — store it immediately as it will not be retrievable again. - Secret string `json:"secret" yaml:"secret"` - Status string `json:"status" yaml:"status"` - - // WebhookSecret Metadata for an HMAC secret. The plaintext value is never included. - WebhookSecret *WebhookSecretInfo `json:"webhookSecret,omitempty" yaml:"webhookSecret,omitempty"` -} - -// WebhookSecretInfo Metadata for an HMAC secret. The plaintext value is never included. -type WebhookSecretInfo struct { - CreatedAt *time.Time `json:"createdAt,omitempty" yaml:"createdAt,omitempty"` - - // DisplayName Human-readable label. - DisplayName *string `json:"displayName,omitempty" yaml:"displayName,omitempty"` - - // Name URL-safe slug (immutable, used as path parameter for regenerate/delete). - Name *string `json:"name,omitempty" yaml:"name,omitempty"` - Status *WebhookSecretInfoStatus `json:"status,omitempty" yaml:"status,omitempty"` - UpdatedAt *time.Time `json:"updatedAt,omitempty" yaml:"updatedAt,omitempty"` -} - -// WebhookSecretInfoStatus defines model for WebhookSecretInfo.Status. -type WebhookSecretInfoStatus string - -// WebhookSecretListResponse defines model for WebhookSecretListResponse. -type WebhookSecretListResponse struct { - Secrets *[]WebhookSecretInfo `json:"secrets,omitempty" yaml:"secrets,omitempty"` - Status *string `json:"status,omitempty" yaml:"status,omitempty"` - - // TotalCount Total number of active secrets for this API - TotalCount *int `json:"totalCount,omitempty" yaml:"totalCount,omitempty"` -} - // ListLLMProviderTemplatesParams defines parameters for ListLLMProviderTemplates. type ListLLMProviderTemplatesParams struct { // DisplayName Filter by template display name @@ -2152,39 +1784,6 @@ 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 - DisplayName *string `form:"displayName,omitempty" json:"displayName,omitempty" yaml:"displayName,omitempty"` - - // Version Filter by WebSub API version - Version *string `form:"version,omitempty" json:"version,omitempty" yaml:"version,omitempty"` - - // Context Filter by WebSub API context/path - Context *string `form:"context,omitempty" json:"context,omitempty" yaml:"context,omitempty"` - - // Status Filter by deployment status - Status *ListWebSubAPIsParamsStatus `form:"status,omitempty" json:"status,omitempty" yaml:"status,omitempty"` -} - -// ListWebSubAPIsParamsStatus defines parameters for ListWebSubAPIs. -type ListWebSubAPIsParamsStatus string - // UploadCertificateJSONRequestBody defines body for UploadCertificate for application/json ContentType. type UploadCertificateJSONRequestBody = CertificateUploadRequest @@ -2263,36 +1862,6 @@ 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 - -// CreateWebBrokerAPIKeyJSONRequestBody defines body for CreateWebBrokerAPIKey for application/json ContentType. -type CreateWebBrokerAPIKeyJSONRequestBody = APIKeyCreationRequest - -// UpdateWebBrokerAPIKeyJSONRequestBody defines body for UpdateWebBrokerAPIKey for application/json ContentType. -type UpdateWebBrokerAPIKeyJSONRequestBody = APIKeyUpdateRequest - -// RegenerateWebBrokerAPIKeyJSONRequestBody defines body for RegenerateWebBrokerAPIKey for application/json ContentType. -type RegenerateWebBrokerAPIKeyJSONRequestBody = APIKeyRegenerationRequest - -// CreateWebSubAPIJSONRequestBody defines body for CreateWebSubAPI for application/json ContentType. -type CreateWebSubAPIJSONRequestBody = WebSubAPIRequest - -// UpdateWebSubAPIJSONRequestBody defines body for UpdateWebSubAPI for application/json ContentType. -type UpdateWebSubAPIJSONRequestBody = WebSubAPIRequest - -// CreateWebSubAPIKeyJSONRequestBody defines body for CreateWebSubAPIKey for application/json ContentType. -type CreateWebSubAPIKeyJSONRequestBody = APIKeyCreationRequest - -// UpdateWebSubAPIKeyJSONRequestBody defines body for UpdateWebSubAPIKey for application/json ContentType. -type UpdateWebSubAPIKeyJSONRequestBody = APIKeyUpdateRequest - -// RegenerateWebSubAPIKeyJSONRequestBody defines body for RegenerateWebSubAPIKey for application/json ContentType. -type RegenerateWebSubAPIKeyJSONRequestBody = APIKeyRegenerationRequest - -// CreateWebSubAPISecretJSONRequestBody defines body for CreateWebSubAPISecret for application/json ContentType. -type CreateWebSubAPISecretJSONRequestBody = WebhookSecretCreationRequest - // AsLLMProviderConfigDataUpstream0 returns the union data inside the LLMProviderConfigData_Upstream as a LLMProviderConfigDataUpstream0 func (t LLMProviderConfigData_Upstream) AsLLMProviderConfigDataUpstream0() (LLMProviderConfigDataUpstream0, error) { var body LLMProviderConfigDataUpstream0 @@ -2872,75 +2441,6 @@ type ServerInterface interface { // Update a subscription // (PUT /subscriptions/{subscriptionId}) UpdateSubscription(w http.ResponseWriter, r *http.Request, subscriptionId string) - // List all WebBrokerAPIs - // (GET /webbroker-apis) - ListWebBrokerApis(w http.ResponseWriter, r *http.Request, params ListWebBrokerApisParams) - // Create a new WebBrokerAPI - // (POST /webbroker-apis) - CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) - // Delete a WebBrokerAPI - // (DELETE /webbroker-apis/{id}) - DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) - // Get WebBrokerAPI by id - // (GET /webbroker-apis/{id}) - GetWebBrokerApiById(w http.ResponseWriter, r *http.Request, id string) - // Get the list of API keys for a WebBroker API - // (GET /webbroker-apis/{id}/api-keys) - ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request, id string) - // Create a new API key for a WebBroker API - // (POST /webbroker-apis/{id}/api-keys) - CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string) - // Revoke an API key for a WebBroker API - // (DELETE /webbroker-apis/{id}/api-keys/{apiKeyName}) - RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) - // Update an API key for a WebBroker API - // (PUT /webbroker-apis/{id}/api-keys/{apiKeyName}) - UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) - // Regenerate API key for a WebBroker API - // (POST /webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate) - RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) - // List all WebSubAPIs - // (GET /websub-apis) - ListWebSubAPIs(w http.ResponseWriter, r *http.Request, params ListWebSubAPIsParams) - // Create a new WebSubAPI - // (POST /websub-apis) - CreateWebSubAPI(w http.ResponseWriter, r *http.Request) - // Delete a WebSubAPI - // (DELETE /websub-apis/{id}) - DeleteWebSubAPI(w http.ResponseWriter, r *http.Request, id string) - // Get WebSubAPI by id - // (GET /websub-apis/{id}) - GetWebSubAPIById(w http.ResponseWriter, r *http.Request, id string) - // Update an existing WebSubAPI - // (PUT /websub-apis/{id}) - UpdateWebSubAPI(w http.ResponseWriter, r *http.Request, id string) - // Get the list of API keys for a WebSub API - // (GET /websub-apis/{id}/api-keys) - ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request, id string) - // Create a new API key for a WebSub API - // (POST /websub-apis/{id}/api-keys) - CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string) - // Revoke an API key for a WebSub API - // (DELETE /websub-apis/{id}/api-keys/{apiKeyName}) - RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) - // Update an API key for a WebSub API - // (PUT /websub-apis/{id}/api-keys/{apiKeyName}) - UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) - // Regenerate API key for a WebSub API - // (POST /websub-apis/{id}/api-keys/{apiKeyName}/regenerate) - RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Request, id string, apiKeyName string) - // List HMAC secrets for a WebSub API - // (GET /websub-apis/{id}/secrets) - ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request, id string) - // Generate a new HMAC secret for a WebSub API - // (POST /websub-apis/{id}/secrets) - CreateWebSubAPISecret(w http.ResponseWriter, r *http.Request, id string) - // Delete a WebSub API HMAC secret - // (DELETE /websub-apis/{id}/secrets/{secretName}) - DeleteWebSubAPISecret(w http.ResponseWriter, r *http.Request, id string, secretName string) - // Regenerate (rotate) a WebSub API HMAC secret - // (POST /websub-apis/{id}/secrets/{secretName}/regenerate) - RegenerateWebSubAPISecret(w http.ResponseWriter, r *http.Request, id string, secretName string) } // ServerInterfaceWrapper converts contexts to parameters. @@ -4859,813 +4359,6 @@ func (siw *ServerInterfaceWrapper) UpdateSubscription(w http.ResponseWriter, r * handler.ServeHTTP(w, r) } -// ListWebBrokerApis operation middleware -func (siw *ServerInterfaceWrapper) ListWebBrokerApis(w http.ResponseWriter, r *http.Request) { - - var err error - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - // 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", r.URL.Query(), ¶ms.DisplayName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "displayName", Err: err}) - return - } - - // ------------- Optional query parameter "version" ------------- - - err = runtime.BindQueryParameter("form", true, false, "version", r.URL.Query(), ¶ms.Version) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "version", Err: err}) - return - } - - // ------------- Optional query parameter "status" ------------- - - err = runtime.BindQueryParameter("form", true, false, "status", r.URL.Query(), ¶ms.Status) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) - return - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWebBrokerApis(w, r, params) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// CreateWebBrokerApi operation middleware -func (siw *ServerInterfaceWrapper) CreateWebBrokerApi(w http.ResponseWriter, r *http.Request) { - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWebBrokerApi(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// DeleteWebBrokerApiById operation middleware -func (siw *ServerInterfaceWrapper) DeleteWebBrokerApiById(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteWebBrokerApiById(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// GetWebBrokerApiById operation middleware -func (siw *ServerInterfaceWrapper) GetWebBrokerApiById(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetWebBrokerApiById(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// ListWebBrokerAPIKeys operation middleware -func (siw *ServerInterfaceWrapper) ListWebBrokerAPIKeys(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWebBrokerAPIKeys(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// CreateWebBrokerAPIKey operation middleware -func (siw *ServerInterfaceWrapper) CreateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWebBrokerAPIKey(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// RevokeWebBrokerAPIKey operation middleware -func (siw *ServerInterfaceWrapper) RevokeWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "apiKeyName" ------------- - var apiKeyName string - - err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.RevokeWebBrokerAPIKey(w, r, id, apiKeyName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// UpdateWebBrokerAPIKey operation middleware -func (siw *ServerInterfaceWrapper) UpdateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "apiKeyName" ------------- - var apiKeyName string - - err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateWebBrokerAPIKey(w, r, id, apiKeyName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// RegenerateWebBrokerAPIKey operation middleware -func (siw *ServerInterfaceWrapper) RegenerateWebBrokerAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "apiKeyName" ------------- - var apiKeyName string - - err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.RegenerateWebBrokerAPIKey(w, r, id, apiKeyName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// ListWebSubAPIs operation middleware -func (siw *ServerInterfaceWrapper) ListWebSubAPIs(w http.ResponseWriter, r *http.Request) { - - var err error - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - // Parameter object where we will unmarshal all parameters from the context - var params ListWebSubAPIsParams - - // ------------- Optional query parameter "displayName" ------------- - - err = runtime.BindQueryParameter("form", true, false, "displayName", r.URL.Query(), ¶ms.DisplayName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "displayName", Err: err}) - return - } - - // ------------- Optional query parameter "version" ------------- - - err = runtime.BindQueryParameter("form", true, false, "version", r.URL.Query(), ¶ms.Version) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "version", Err: err}) - return - } - - // ------------- Optional query parameter "context" ------------- - - err = runtime.BindQueryParameter("form", true, false, "context", r.URL.Query(), ¶ms.Context) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "context", Err: err}) - return - } - - // ------------- Optional query parameter "status" ------------- - - err = runtime.BindQueryParameter("form", true, false, "status", r.URL.Query(), ¶ms.Status) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) - return - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWebSubAPIs(w, r, params) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// CreateWebSubAPI operation middleware -func (siw *ServerInterfaceWrapper) CreateWebSubAPI(w http.ResponseWriter, r *http.Request) { - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWebSubAPI(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// DeleteWebSubAPI operation middleware -func (siw *ServerInterfaceWrapper) DeleteWebSubAPI(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteWebSubAPI(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// GetWebSubAPIById operation middleware -func (siw *ServerInterfaceWrapper) GetWebSubAPIById(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetWebSubAPIById(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// UpdateWebSubAPI operation middleware -func (siw *ServerInterfaceWrapper) UpdateWebSubAPI(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateWebSubAPI(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// ListWebSubAPIKeys operation middleware -func (siw *ServerInterfaceWrapper) ListWebSubAPIKeys(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWebSubAPIKeys(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// CreateWebSubAPIKey operation middleware -func (siw *ServerInterfaceWrapper) CreateWebSubAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWebSubAPIKey(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// RevokeWebSubAPIKey operation middleware -func (siw *ServerInterfaceWrapper) RevokeWebSubAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "apiKeyName" ------------- - var apiKeyName string - - err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.RevokeWebSubAPIKey(w, r, id, apiKeyName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// UpdateWebSubAPIKey operation middleware -func (siw *ServerInterfaceWrapper) UpdateWebSubAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "apiKeyName" ------------- - var apiKeyName string - - err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateWebSubAPIKey(w, r, id, apiKeyName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// RegenerateWebSubAPIKey operation middleware -func (siw *ServerInterfaceWrapper) RegenerateWebSubAPIKey(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "apiKeyName" ------------- - var apiKeyName string - - err = runtime.BindStyledParameterWithOptions("simple", "apiKeyName", r.PathValue("apiKeyName"), &apiKeyName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "apiKeyName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.RegenerateWebSubAPIKey(w, r, id, apiKeyName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// ListWebSubAPISecrets operation middleware -func (siw *ServerInterfaceWrapper) ListWebSubAPISecrets(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListWebSubAPISecrets(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// CreateWebSubAPISecret operation middleware -func (siw *ServerInterfaceWrapper) CreateWebSubAPISecret(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateWebSubAPISecret(w, r, id) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// DeleteWebSubAPISecret operation middleware -func (siw *ServerInterfaceWrapper) DeleteWebSubAPISecret(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "secretName" ------------- - var secretName string - - err = runtime.BindStyledParameterWithOptions("simple", "secretName", r.PathValue("secretName"), &secretName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "secretName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteWebSubAPISecret(w, r, id, secretName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// RegenerateWebSubAPISecret operation middleware -func (siw *ServerInterfaceWrapper) RegenerateWebSubAPISecret(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - // ------------- Path parameter "secretName" ------------- - var secretName string - - err = runtime.BindStyledParameterWithOptions("simple", "secretName", r.PathValue("secretName"), &secretName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "secretName", Err: err}) - return - } - - ctx := r.Context() - - ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) - - r = r.WithContext(ctx) - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.RegenerateWebSubAPISecret(w, r, id, secretName) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - type UnescapedCookieParamError struct { ParamName string Err error @@ -5845,29 +4538,6 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc("DELETE "+options.BaseURL+"/subscriptions/{subscriptionId}", wrapper.DeleteSubscription) m.HandleFunc("GET "+options.BaseURL+"/subscriptions/{subscriptionId}", wrapper.GetSubscription) m.HandleFunc("PUT "+options.BaseURL+"/subscriptions/{subscriptionId}", wrapper.UpdateSubscription) - m.HandleFunc("GET "+options.BaseURL+"/webbroker-apis", wrapper.ListWebBrokerApis) - m.HandleFunc("POST "+options.BaseURL+"/webbroker-apis", wrapper.CreateWebBrokerApi) - m.HandleFunc("DELETE "+options.BaseURL+"/webbroker-apis/{id}", wrapper.DeleteWebBrokerApiById) - m.HandleFunc("GET "+options.BaseURL+"/webbroker-apis/{id}", wrapper.GetWebBrokerApiById) - m.HandleFunc("GET "+options.BaseURL+"/webbroker-apis/{id}/api-keys", wrapper.ListWebBrokerAPIKeys) - m.HandleFunc("POST "+options.BaseURL+"/webbroker-apis/{id}/api-keys", wrapper.CreateWebBrokerAPIKey) - m.HandleFunc("DELETE "+options.BaseURL+"/webbroker-apis/{id}/api-keys/{apiKeyName}", wrapper.RevokeWebBrokerAPIKey) - m.HandleFunc("PUT "+options.BaseURL+"/webbroker-apis/{id}/api-keys/{apiKeyName}", wrapper.UpdateWebBrokerAPIKey) - m.HandleFunc("POST "+options.BaseURL+"/webbroker-apis/{id}/api-keys/{apiKeyName}/regenerate", wrapper.RegenerateWebBrokerAPIKey) - m.HandleFunc("GET "+options.BaseURL+"/websub-apis", wrapper.ListWebSubAPIs) - m.HandleFunc("POST "+options.BaseURL+"/websub-apis", wrapper.CreateWebSubAPI) - m.HandleFunc("DELETE "+options.BaseURL+"/websub-apis/{id}", wrapper.DeleteWebSubAPI) - m.HandleFunc("GET "+options.BaseURL+"/websub-apis/{id}", wrapper.GetWebSubAPIById) - m.HandleFunc("PUT "+options.BaseURL+"/websub-apis/{id}", wrapper.UpdateWebSubAPI) - m.HandleFunc("GET "+options.BaseURL+"/websub-apis/{id}/api-keys", wrapper.ListWebSubAPIKeys) - m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/api-keys", wrapper.CreateWebSubAPIKey) - m.HandleFunc("DELETE "+options.BaseURL+"/websub-apis/{id}/api-keys/{apiKeyName}", wrapper.RevokeWebSubAPIKey) - m.HandleFunc("PUT "+options.BaseURL+"/websub-apis/{id}/api-keys/{apiKeyName}", wrapper.UpdateWebSubAPIKey) - m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/api-keys/{apiKeyName}/regenerate", wrapper.RegenerateWebSubAPIKey) - m.HandleFunc("GET "+options.BaseURL+"/websub-apis/{id}/secrets", wrapper.ListWebSubAPISecrets) - m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/secrets", wrapper.CreateWebSubAPISecret) - m.HandleFunc("DELETE "+options.BaseURL+"/websub-apis/{id}/secrets/{secretName}", wrapper.DeleteWebSubAPISecret) - m.HandleFunc("POST "+options.BaseURL+"/websub-apis/{id}/secrets/{secretName}/regenerate", wrapper.RegenerateWebSubAPISecret) return m } @@ -5875,311 +4545,258 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AAD//7xP39op1QEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/gateway/gateway-controller/pkg/config/api_validator.go b/gateway/gateway-controller/pkg/config/api_validator.go index 7959be0eb5..adc7d698c3 100644 --- a/gateway/gateway-controller/pkg/config/api_validator.go +++ b/gateway/gateway-controller/pkg/config/api_validator.go @@ -67,18 +67,11 @@ func (v *APIValidator) Validate(config interface{}) []ValidationError { return v.validateRestAPIConfiguration(cfg) case api.RestAPI: return v.validateRestAPIConfiguration(&cfg) - case *api.WebSubAPI: - if cfg == nil { - return []ValidationError{{Field: "config", Message: "WebSubAPI configuration is nil"}} - } - return v.validateWebSubAPIConfiguration(cfg) - case api.WebSubAPI: - return v.validateWebSubAPIConfiguration(&cfg) default: return []ValidationError{ { Field: "config", - Message: "Unsupported configuration type for APIValidator (expected RestAPI or WebSubAPI)", + Message: "Unsupported configuration type for APIValidator (expected RestAPI)", }, } } @@ -119,35 +112,6 @@ func (v *APIValidator) validateRestAPIConfiguration(config *api.RestAPI) []Valid return errors } -// validateWebSubAPIConfiguration performs comprehensive validation on a WebSub API configuration -func (v *APIValidator) validateWebSubAPIConfiguration(config *api.WebSubAPI) []ValidationError { - var errors []ValidationError - - // Validate kind - if config.Kind != api.WebSubAPIKindWebSubApi { - errors = append(errors, ValidationError{ - Field: "kind", - Message: "Unsupported kind (must be 'WebSubApi')", - }) - } - - // Validate version - if config.ApiVersion != api.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1 { - errors = append(errors, ValidationError{ - Field: "version", - Message: "Unsupported API version (must be 'gateway.api-platform.wso2.com/v1')", - }) - } - - // Validate data section - errors = append(errors, v.validateAsyncData(&config.Spec)...) - - // Validate metadata (including labels) - errors = append(errors, ValidateMetadata(&config.Metadata)...) - - return errors -} - // validateUpstream validates a single upstream definition (main or sandbox) func (v *APIValidator) validateUpstream(label string, up *api.Upstream, upstreamDefinitions *[]api.UpstreamDefinition) []ValidationError { var errors []ValidationError @@ -488,7 +452,7 @@ func (v *APIValidator) validateRestData(spec *api.APIConfigData) []ValidationErr } // Validate context - errors = append(errors, v.validateContext(spec.Context)...) + errors = append(errors, v.ValidateContext(spec.Context)...) // Validate upstreamDefinitions first errors = append(errors, v.validateUpstreamDefinitions(spec.UpstreamDefinitions)...) @@ -556,88 +520,11 @@ func validateResilienceTimeouts(fieldPrefix string, r *api.Resilience) []Validat return errors } -// validateAsyncData validates the data section of the configuration for http/rest kind -func (v *APIValidator) validateAsyncData(spec *api.WebhookAPIData) []ValidationError { - var errors []ValidationError - - // Validate name - if spec.DisplayName == "" { - errors = append(errors, ValidationError{ - Field: "spec.name", - Message: "API name is required", - }) - } else if len(spec.DisplayName) > 100 { - errors = append(errors, ValidationError{ - Field: "spec.name", - Message: "API name must be 1-100 characters", - }) - } else if !v.urlFriendlyNameRegex.MatchString(spec.DisplayName) { - errors = append(errors, ValidationError{ - Field: "spec.name", - Message: "API name must be URL-friendly (only letters, numbers, spaces, hyphens, underscores, and dots allowed)", - }) - } - - // Validate version - if spec.Version == "" { - errors = append(errors, ValidationError{ - Field: "spec.version", - Message: "API version is required", - }) - } else if !v.versionRegex.MatchString(spec.Version) { - errors = append(errors, ValidationError{ - Field: "spec.version", - Message: "API version must follow semantic versioning pattern (e.g., v1.0, v2.1.3)", - }) - } - - // Validate context - errors = append(errors, v.validateContext(spec.Context)...) - - // Validate channel policies - var channels map[string]api.WebSubChannel - if spec.Channels != nil { - channels = *spec.Channels - } - errors = append(errors, v.validateChannelPolicies(channels)...) - - return errors -} - -// validateChannelPolicies validates the channels map configuration -func (v *APIValidator) validateChannelPolicies(channelPolicies map[string]api.WebSubChannel) []ValidationError { - var errors []ValidationError - - if len(channelPolicies) == 0 { - errors = append(errors, ValidationError{ - Field: "spec.channels", - Message: "At least one channel is required", - }) - return errors - } - - for chName := range channelPolicies { - if strings.TrimSpace(chName) == "" { - errors = append(errors, ValidationError{ - Field: "spec.channels", - Message: "Channel name (key) must not be empty", - }) - continue - } - - if !v.validatePathParametersForAsyncAPIs(chName) { - errors = append(errors, ValidationError{ - Field: fmt.Sprintf("spec.channels.%s", chName), - Message: "Channel name has {} in parameters", - }) - } - } - - return errors -} - // validateContext validates the context path -func (v *APIValidator) validateContext(context string) []ValidationError { +// ValidateContext validates a resource's context path. Exported so it can be +// reused by other kinds' validators (e.g. an event-gateway-controller binary +// validating WebSubApi/WebBrokerApi configs). +func (v *APIValidator) ValidateContext(context string) []ValidationError { var errors []ValidationError if context == "" { @@ -672,15 +559,6 @@ func (v *APIValidator) validateContext(context string) []ValidationError { return errors } -// validatePathParametersForAsyncAPIs returns true when the path does not contain '{' or '}'. -// Async/WebSub channel paths do not currently support templated path parameters. -func (v *APIValidator) validatePathParametersForAsyncAPIs(path string) bool { - - openCount := strings.Count(path, "{") - closeCount := strings.Count(path, "}") - return openCount == 0 && closeCount == 0 -} - // validateOperations validates the operations configuration func (v *APIValidator) validateOperations(operations []api.Operation) []ValidationError { var errors []ValidationError diff --git a/gateway/gateway-controller/pkg/config/api_validator_test.go b/gateway/gateway-controller/pkg/config/api_validator_test.go index 8719c0ac7c..93cbb5da49 100644 --- a/gateway/gateway-controller/pkg/config/api_validator_test.go +++ b/gateway/gateway-controller/pkg/config/api_validator_test.go @@ -145,22 +145,6 @@ func TestAPIValidator_ValidateKind(t *testing.T) { } }) - // Test WebSubApi kind - valid - t.Run("Valid WebSubApi kind", func(t *testing.T) { - config := createValidWebSubAPIConfig() - errors := v.Validate(config) - hasKindError := false - for _, e := range errors { - if e.Field == "kind" { - hasKindError = true - break - } - } - if hasKindError { - t.Error("unexpected kind error") - } - }) - // Test unsupported type t.Run("Unsupported type", func(t *testing.T) { errors := v.Validate("InvalidKind") @@ -186,22 +170,6 @@ func TestAPIValidator_ValidateKind(t *testing.T) { } }) - // Test invalid Kind on WebSubAPI - t.Run("Invalid WebSubApi kind", func(t *testing.T) { - config := createValidWebSubAPIConfig() - config.Kind = "InvalidKind" - errors := v.Validate(config) - hasKindError := false - for _, e := range errors { - if e.Field == "kind" { - hasKindError = true - break - } - } - if !hasKindError { - t.Error("expected kind error for invalid WebSubAPI kind, got none") - } - }) } func TestAPIValidator_ValidateDisplayName(t *testing.T) { @@ -562,159 +530,6 @@ func TestAPIValidator_ValidateAllHTTPMethods(t *testing.T) { } } -func TestAPIValidator_ValidateWebSubAPI(t *testing.T) { - v := NewAPIValidator() - - config := createValidWebSubAPIConfig() - - errors := v.Validate(config) - if len(errors) != 0 { - t.Errorf("expected no errors for valid WebSubApi, got: %v", errors) - } -} - -func TestAPIValidator_ValidateChannels(t *testing.T) { - v := NewAPIValidator() - - tests := []struct { - name string - channels map[string]api.WebSubChannel - wantError bool - errField string - }{ - { - name: "Valid channels", - channels: map[string]api.WebSubChannel{ - "channel1": {}, - "channel2": {}, - }, - wantError: false, - }, - { - name: "Empty channels", - channels: map[string]api.WebSubChannel{}, - wantError: true, - errField: "spec.channels", - }, - { - name: "Channel with braces (invalid)", - channels: map[string]api.WebSubChannel{ - "channel/{id}": {}, - }, - wantError: true, - errField: "spec.channels.channel/{id}", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := createValidWebSubAPIConfig() - config.Spec.Channels = &tt.channels - - errors := v.Validate(config) - hasExpectedError := false - for _, e := range errors { - if strings.HasPrefix(e.Field, tt.errField) { - hasExpectedError = true - break - } - } - if tt.wantError && !hasExpectedError { - t.Errorf("expected error for field %s, got: %v", tt.errField, errors) - } - }) - } -} - -func TestAPIValidator_ValidateAsyncDisplayName(t *testing.T) { - v := NewAPIValidator() - - tests := []struct { - name string - displayName string - wantError bool - }{ - {name: "Valid name", displayName: "MyWebSub", wantError: false}, - {name: "Empty name", displayName: "", wantError: true}, - {name: "Name too long", displayName: strings.Repeat("a", 101), wantError: true}, - {name: "Invalid characters", displayName: "test@#$", wantError: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := createValidWebSubAPIConfig() - config.Spec.DisplayName = tt.displayName - - errors := v.Validate(config) - hasNameError := false - for _, e := range errors { - if e.Field == "spec.name" { - hasNameError = true - break - } - } - if tt.wantError && !hasNameError { - t.Error("expected name error, got none") - } - if !tt.wantError && hasNameError { - t.Error("unexpected name error") - } - }) - } -} - -func TestAPIValidator_ValidatePathParameters(t *testing.T) { - v := NewAPIValidator() - - tests := []struct { - path string - expected bool - }{ - {"/items/{id}", true}, - {"/items/{id}/sub/{subId}", true}, - {"/items", true}, - {"/items/{id", false}, - {"/items/id}", false}, - {"/items/{id}/{", false}, - } - - for _, tt := range tests { - t.Run(tt.path, func(t *testing.T) { - result := v.validatePathParameters(tt.path) - if result != tt.expected { - t.Errorf("validatePathParameters(%s) = %v, want %v", tt.path, result, tt.expected) - } - }) - } -} - -func TestAPIValidator_ValidatePathParametersForAsyncAPIs(t *testing.T) { - v := NewAPIValidator() - - tests := []struct { - path string - expected bool - }{ - {"channel1", true}, - {"my-channel", true}, - {"channel/{id}", false}, - {"{channel}", false}, - {"channel}", false}, - {"channel{", false}, - } - - for _, tt := range tests { - t.Run(tt.path, func(t *testing.T) { - result := v.validatePathParametersForAsyncAPIs(tt.path) - if result != tt.expected { - t.Errorf("validatePathParametersForAsyncAPIs(%s) = %v, want %v", tt.path, result, tt.expected) - } - }) - } -} - -// Helper functions - func createValidRestAPIConfig() *api.RestAPI { return &api.RestAPI{ ApiVersion: api.RestAPIApiVersionGatewayApiPlatformWso2Comv1, @@ -740,21 +555,3 @@ func createValidRestAPIConfig() *api.RestAPI { }, } } - -func createValidWebSubAPIConfig() *api.WebSubAPI { - return &api.WebSubAPI{ - ApiVersion: api.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1, - Kind: api.WebSubAPIKindWebSubApi, - Metadata: api.Metadata{ - Name: "test-websub", - }, - Spec: api.WebhookAPIData{ - DisplayName: "Test WebSub", - Version: "v1.0", - Context: "/websub", - Channels: &map[string]api.WebSubChannel{ - "channel1": {}, - }, - }, - } -} diff --git a/gateway/gateway-controller/pkg/config/config.go b/gateway/gateway-controller/pkg/config/config.go index 5a1e75aaf4..d0c7568ca0 100644 --- a/gateway/gateway-controller/pkg/config/config.go +++ b/gateway/gateway-controller/pkg/config/config.go @@ -21,7 +21,6 @@ package config import ( "fmt" "log/slog" - "net/url" "strconv" "strings" "time" @@ -533,7 +532,6 @@ type RouterConfig struct { Upstream RouterUpstream `koanf:"upstream"` PolicyEngine PolicyEngineConfig `koanf:"policy_engine"` DownstreamTLS DownstreamTLS `koanf:"downstream_tls"` - EventGateway EventGatewayConfig `koanf:"event_gateway"` VHosts VHostsConfig `koanf:"vhosts"` TracingServiceName string `koanf:"tracing_service_name"` @@ -575,16 +573,6 @@ type LuaScriptConfig struct { ScriptPath string `koanf:"script_path"` } -// EventGatewayConfig holds event gateway specific configurations -type EventGatewayConfig struct { - Enabled bool `koanf:"enabled"` - WebSubHubURL string `koanf:"websub_hub_url"` - WebSubHubPort int `koanf:"websub_hub_port"` - RouterHost string `koanf:"router_host"` - WebSubHubListenerPort int `koanf:"websub_hub_listener_port"` - TimeoutSeconds int `koanf:"timeout_seconds"` -} - // DownstreamTLS holds downstream (listener) TLS configuration type DownstreamTLS struct { CertPath string `koanf:"cert_path"` @@ -952,14 +940,6 @@ func defaultConfig() *Config { }, }, Router: RouterConfig{ - EventGateway: EventGatewayConfig{ - Enabled: false, - WebSubHubURL: "http://host.docker.internal", - WebSubHubPort: 9098, - RouterHost: "localhost", - WebSubHubListenerPort: 8083, - TimeoutSeconds: 30, - }, AccessLogs: AccessLogsConfig{ Enabled: true, Format: "text", @@ -1424,13 +1404,6 @@ func (c *Config) Validate() error { return fmt.Errorf("event_hub.retention_period must be positive, got: %s", eh.RetentionPeriod) } - // Validate event gateway configuration if enabled - if c.Router.EventGateway.Enabled { - if err := c.validateEventGatewayConfig(); err != nil { - return err - } - } - // Validate control plane configuration if err := c.validateControlPlaneConfig(); err != nil { return err @@ -1482,29 +1455,6 @@ func (c *Config) Validate() error { return nil } -func (c *Config) validateEventGatewayConfig() error { - if c.Router.EventGateway.WebSubHubPort < 1 || c.Router.EventGateway.WebSubHubPort > 65535 { - return fmt.Errorf("router.event_gateway.websub_hub_port must be between 1 and 65535, got: %d", c.Router.EventGateway.WebSubHubPort) - } - if c.Router.EventGateway.WebSubHubListenerPort < 1 || c.Router.EventGateway.WebSubHubListenerPort > 65535 { - return fmt.Errorf("router.event_gateway.websub_hub_listener_port must be between 1 and 65535, got: %d", c.Router.EventGateway.WebSubHubListenerPort) - } - - // Validate WebSubHubURL if provided - must be a valid http(s) URL - if strings.TrimSpace(c.Router.EventGateway.WebSubHubURL) != "" { - u, err := url.Parse(c.Router.EventGateway.WebSubHubURL) - if err != nil || (u.Scheme != "http" && u.Scheme != "https") { - return fmt.Errorf("router.event_gateway.websub_hub_url must be a valid URL with http or https scheme, got: %s", c.Router.EventGateway.WebSubHubURL) - } - if u.Host == "" { - return fmt.Errorf("router.event_gateway.websub_hub_url must include a valid host, got: %s", c.Router.EventGateway.WebSubHubURL) - } - } - if c.Router.EventGateway.TimeoutSeconds <= 0 { - return fmt.Errorf("router.event_gateway.timeout_seconds must be positive, got: %d", c.Router.EventGateway.TimeoutSeconds) - } - return nil -} // validateControlPlaneConfig validates the control plane configuration func (c *Config) validateControlPlaneConfig() error { diff --git a/gateway/gateway-controller/pkg/config/config_test.go b/gateway/gateway-controller/pkg/config/config_test.go index 6a23cfe474..504b9158ec 100644 --- a/gateway/gateway-controller/pkg/config/config_test.go +++ b/gateway/gateway-controller/pkg/config/config_test.go @@ -758,41 +758,6 @@ func TestConfig_Validate_HTTPSPort(t *testing.T) { } } -func TestConfig_ValidateEventGatewayConfig(t *testing.T) { - tests := []struct { - name string - webSubHubPort int - webSubHubListenerPort int - timeoutSeconds int - wantErr bool - errContains string - }{ - {name: "Valid ports", webSubHubPort: 9500, webSubHubListenerPort: 9501, timeoutSeconds: 30, wantErr: false}, - {name: "Invalid hub port low", webSubHubPort: 0, webSubHubListenerPort: 9501, timeoutSeconds: 30, wantErr: true, errContains: "websub_hub_port must be between"}, - {name: "Invalid hub port high", webSubHubPort: 70000, webSubHubListenerPort: 9501, timeoutSeconds: 30, wantErr: true, errContains: "websub_hub_port must be between"}, - {name: "Invalid listener port low", webSubHubPort: 9500, webSubHubListenerPort: 0, timeoutSeconds: 30, wantErr: true, errContains: "websub_hub_listener_port must be between"}, - {name: "Invalid listener port high", webSubHubPort: 9500, webSubHubListenerPort: 70000, timeoutSeconds: 30, wantErr: true, errContains: "websub_hub_listener_port must be between"}, - {name: "Invalid timeout", webSubHubPort: 9500, webSubHubListenerPort: 9501, timeoutSeconds: 0, wantErr: true, errContains: "timeout_seconds must be positive"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg := validConfig() - cfg.Router.EventGateway.Enabled = true - cfg.Router.EventGateway.WebSubHubPort = tt.webSubHubPort - cfg.Router.EventGateway.WebSubHubListenerPort = tt.webSubHubListenerPort - cfg.Router.EventGateway.TimeoutSeconds = tt.timeoutSeconds - err := cfg.Validate() - if tt.wantErr { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.errContains) - } else { - assert.NoError(t, err) - } - }) - } -} - func TestConfig_ValidateControlPlaneConfig(t *testing.T) { tests := []struct { name string diff --git a/gateway/gateway-controller/pkg/config/parser.go b/gateway/gateway-controller/pkg/config/parser.go index 3dbaba0cb4..72c56b25f9 100644 --- a/gateway/gateway-controller/pkg/config/parser.go +++ b/gateway/gateway-controller/pkg/config/parser.go @@ -56,21 +56,6 @@ func (p *Parser) ParseAPIConfigYAML(data []byte, configParsed interface{}) error } *target = config return nil - case *api.WebSubAPI: - var config api.WebSubAPI - var intermediate map[string]interface{} - if err := yaml.Unmarshal(data, &intermediate); err != nil { - return fmt.Errorf("failed to unmarshal YAML: %w", err) - } - jsonBytes, err := json.Marshal(intermediate) - if err != nil { - return fmt.Errorf("failed to marshal intermediate to JSON: %w", err) - } - if err := p.ParseJSON(jsonBytes, &config); err != nil { - return fmt.Errorf("failed to unmarshal JSON into WebSubAPI: %w", err) - } - *target = config - return nil default: _ = target if err := yaml.Unmarshal(data, target); err != nil { diff --git a/gateway/gateway-controller/pkg/config/validator_test.go b/gateway/gateway-controller/pkg/config/validator_test.go index 93d6a4744c..6a867a98be 100644 --- a/gateway/gateway-controller/pkg/config/validator_test.go +++ b/gateway/gateway-controller/pkg/config/validator_test.go @@ -233,45 +233,6 @@ func TestValidateAuthConfig_BothAuthEnabled(t *testing.T) { assert.NoError(t, err) } -func TestValidateEventGWConfig_Enabled(t *testing.T) { - // Test that validation passes when event gateway is enabled with valid config - config := &Config{ - Controller: Controller{}, - Router: RouterConfig{ - EventGateway: EventGatewayConfig{ - Enabled: true, - WebSubHubURL: "http://example.com", - WebSubHubPort: 9098, - WebSubHubListenerPort: 8083, - TimeoutSeconds: 10, - }, - }, - } - - err := config.validateEventGatewayConfig() - assert.NoError(t, err) -} - -func TestValidateWebSubURLConfig_WithoutSchema(t *testing.T) { - // Test that validation fails when there's no scheme in WebSubHubURL - config := &Config{ - Controller: Controller{}, - Router: RouterConfig{ - EventGateway: EventGatewayConfig{ - Enabled: true, - WebSubHubURL: "example.com", - WebSubHubPort: 9098, - WebSubHubListenerPort: 8083, - TimeoutSeconds: 10, - }, - }, - } - - err := config.validateEventGatewayConfig() - assert.Error(t, err) - assert.Contains(t, err.Error(), "http or https scheme") -} - func TestValidator_LabelsValidation(t *testing.T) { validator := NewAPIValidator() @@ -451,35 +412,7 @@ func TestValidator_LabelsWithAllAPITypes(t *testing.T) { assert.False(t, hasLabelError, "RestApi should accept valid labels") }) - // Test WebSubApi - t.Run("WebSubApi with valid labels", func(t *testing.T) { - config := &api.WebSubAPI{ - ApiVersion: api.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1, - Kind: api.WebSubAPIKindWebSubApi, - Metadata: api.Metadata{ - Name: "test-api-v1.0", - Labels: &validLabels, - }, - Spec: api.WebhookAPIData{ - DisplayName: "TestAPI", - Version: "v1.0", - Context: "/test", - Channels: &map[string]api.WebSubChannel{ - "/events": {}, - }, - }, - } - - errors := validator.Validate(config) - hasLabelError := false - for _, err := range errors { - if err.Field == "metadata.labels" { - hasLabelError = true - break - } - } - assert.False(t, hasLabelError, "WebSubApi should accept valid labels") - }) // Test with invalid labels for both types + // Test with invalid labels for both types invalidLabels := map[string]string{"Invalid Key": "value"} t.Run("RestApi with invalid labels", func(t *testing.T) { @@ -519,34 +452,6 @@ func TestValidator_LabelsWithAllAPITypes(t *testing.T) { assert.True(t, hasLabelError, "RestApi should reject labels with spaces in keys") }) - t.Run("WebSubApi with invalid labels", func(t *testing.T) { - config := &api.WebSubAPI{ - ApiVersion: api.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1, - Kind: api.WebSubAPIKindWebSubApi, - Metadata: api.Metadata{ - Name: "test-api-v1.0", - Labels: &invalidLabels, - }, - Spec: api.WebhookAPIData{ - DisplayName: "TestAPI", - Version: "v1.0", - Context: "/test", - Channels: &map[string]api.WebSubChannel{ - "/events": {}, - }, - }, - } - - errors := validator.Validate(config) - hasLabelError := false - for _, err := range errors { - if err.Field == "metadata.labels" { - hasLabelError = true - break - } - } - assert.True(t, hasLabelError, "WebSubApi should reject labels with spaces in keys") - }) } func TestValidateUpstreamDefinitions_Valid(t *testing.T) { diff --git a/gateway/gateway-controller/pkg/controlplane/client.go b/gateway/gateway-controller/pkg/controlplane/client.go index 82e518f927..83bd1cbe8d 100644 --- a/gateway/gateway-controller/pkg/controlplane/client.go +++ b/gateway/gateway-controller/pkg/controlplane/client.go @@ -47,7 +47,6 @@ import ( "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/version" - "github.com/wso2/api-platform/gateway/gateway-controller/pkg/webhooksecretxds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/xds" ) @@ -109,6 +108,15 @@ type secretSyncer interface { UpsertFromPlatform(handle, displayName, plaintext string) error } +// WebhookSecretSnapshotRefresher is the extension point through which an +// external event-gateway-controller binary supplies webhook-secret xDS +// snapshot refresh. Core never implements this interface itself; it is only +// ever satisfied by a webhooksecretxds.SnapshotManager living outside this +// module. +type WebhookSecretSnapshotRefresher interface { + RefreshSnapshot() error +} + // Client manages the WebSocket connection to the control plane type Client struct { config config.ControlPlaneConfig @@ -143,7 +151,7 @@ type Client struct { syncOnce sync.Once // ensures deployment sync runs only on first connect isFirstConnect atomic.Bool // true on first connect, flipped to false after webhookSecretStore *webhooksecret.WebhookSecretStore - webhookSecretSnapshotManager *webhooksecretxds.SnapshotManager + webhookSecretSnapshotManager WebhookSecretSnapshotRefresher secretSyncer secretSyncer secretHashCache sync.Map // handle → last-known Platform API hash (string) @@ -174,7 +182,7 @@ func NewClient( eventHubInstance eventhub.EventHub, secretResolver funcs.SecretResolver, webhookSecretStore *webhooksecret.WebhookSecretStore, - webhookSecretSnapshotManager *webhooksecretxds.SnapshotManager, + webhookSecretSnapshotManager WebhookSecretSnapshotRefresher, ) *Client { if db == nil { panic("control plane client requires non-nil storage") diff --git a/gateway/gateway-controller/pkg/eventlistener/listener.go b/gateway/gateway-controller/pkg/eventlistener/listener.go index bc0f255c13..8c251d731f 100644 --- a/gateway/gateway-controller/pkg/eventlistener/listener.go +++ b/gateway/gateway-controller/pkg/eventlistener/listener.go @@ -25,18 +25,22 @@ import ( "strings" "github.com/wso2/api-platform/common/eventhub" - "github.com/wso2/api-platform/common/webhooksecret" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" - "github.com/wso2/api-platform/gateway/gateway-controller/pkg/encryption" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/lazyresourcexds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/policyxds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/templateengine/funcs" - "github.com/wso2/api-platform/gateway/gateway-controller/pkg/webhooksecretxds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/xds" ) +// WebhookSecretEventHandler is the extension point through which an external +// event-gateway-controller binary supplies webhook-secret (HMAC) event +// processing. Core never implements this interface itself. +type WebhookSecretEventHandler interface { + HandleEvent(event eventhub.Event) +} + // APIKeyXDSManager defines API key xDS operations used by the listener. type APIKeyXDSManager interface { StoreAPIKey(apiId, apiName, apiVersion string, apiKey *models.APIKey, correlationID string) error @@ -64,10 +68,8 @@ type EventListener struct { logger *slog.Logger systemConfig *config.Config policyDefinitions map[string]models.PolicyDefinition - secretResolver funcs.SecretResolver - webhookSecretStore *webhooksecret.WebhookSecretStore - webhookSecretSnapshotManager *webhooksecretxds.SnapshotManager - providerManager *encryption.ProviderManager + secretResolver funcs.SecretResolver + webhookSecretHandler WebhookSecretEventHandler eventCh <-chan eventhub.Event ctx context.Context @@ -89,9 +91,6 @@ func NewEventListener( systemConfig *config.Config, policyDefinitions map[string]models.PolicyDefinition, secretResolver funcs.SecretResolver, - webhookSecretStore *webhooksecret.WebhookSecretStore, - webhookSecretSnapshotManager *webhooksecretxds.SnapshotManager, - providerManager *encryption.ProviderManager, ) *EventListener { if eventHub == nil { panic("event listener requires non-nil EventHub") @@ -124,15 +123,18 @@ func NewEventListener( logger: logger, systemConfig: systemConfig, policyDefinitions: policyDefinitions, - secretResolver: secretResolver, - webhookSecretStore: webhookSecretStore, - webhookSecretSnapshotManager: webhookSecretSnapshotManager, - providerManager: providerManager, + secretResolver: secretResolver, ctx: ctx, cancel: cancel, } } +// SetWebhookSecretHandler registers the event-gateway webhook-secret event +// handler. Passing nil (the default) means webhook-secret events are ignored. +func (l *EventListener) SetWebhookSecretHandler(h WebhookSecretEventHandler) { + l.webhookSecretHandler = h +} + // Start begins listening for events func (l *EventListener) Start() error { gatewayID := strings.TrimSpace(l.systemConfig.Controller.Server.GatewayID) @@ -231,7 +233,9 @@ func (l *EventListener) handleEvent(event eventhub.Event) { case eventhub.EventTypeMCPProxy: l.processMCPProxyEvent(event) case eventhub.EventTypeWebhookSecret: - l.processWebhookSecretEvent(event) + if l.webhookSecretHandler != nil { + l.webhookSecretHandler.HandleEvent(event) + } default: l.logger.Warn("Unknown event type received", slog.String("event_type", string(event.EventType)), diff --git a/gateway/gateway-controller/pkg/eventlistener/listener_test.go b/gateway/gateway-controller/pkg/eventlistener/listener_test.go index 0765ee808c..a4f4feca37 100644 --- a/gateway/gateway-controller/pkg/eventlistener/listener_test.go +++ b/gateway/gateway-controller/pkg/eventlistener/listener_test.go @@ -256,9 +256,6 @@ func TestNewEventListener_RequiresSystemConfig(t *testing.T) { nil, nil, nil, - nil, - nil, - nil, ) }) } @@ -279,9 +276,6 @@ func TestNewEventListener_RequiresGatewayID(t *testing.T) { &config.Config{Controller: config.Controller{}}, nil, nil, - nil, - nil, - nil, ) }) } @@ -308,9 +302,6 @@ func TestStart_SubscribesWithTrimmedGatewayID(t *testing.T) { }, nil, nil, - nil, - nil, - nil, ) require.NoError(t, listener.Start()) diff --git a/gateway/gateway-controller/pkg/models/stored_config.go b/gateway/gateway-controller/pkg/models/stored_config.go index cd1b5e6852..4decf15948 100644 --- a/gateway/gateway-controller/pkg/models/stored_config.go +++ b/gateway/gateway-controller/pkg/models/stored_config.go @@ -129,10 +129,6 @@ func apiVersionOf(cfg any) string { switch sc := cfg.(type) { case api.RestAPI: return string(sc.ApiVersion) - case api.WebSubAPI: - return string(sc.ApiVersion) - case api.WebBrokerApi: - return string(sc.ApiVersion) case api.LLMProviderConfiguration: return string(sc.ApiVersion) case api.LLMProxyConfiguration: @@ -148,8 +144,6 @@ func (c *StoredConfig) GetContext() (string, error) { switch sc := c.SourceConfiguration.(type) { case api.RestAPI: return strings.ReplaceAll(sc.Spec.Context, "$version", c.Version), nil - case api.WebSubAPI: - return strings.ReplaceAll(sc.Spec.Context, "$version", c.Version), nil case api.LLMProviderConfiguration: if sc.Spec.Context != nil { return strings.ReplaceAll(*sc.Spec.Context, "$version", c.Version), nil @@ -182,8 +176,6 @@ func (c *StoredConfig) GetMetadata() *api.Metadata { switch cfg := c.Configuration.(type) { case api.RestAPI: return &cfg.Metadata - case api.WebSubAPI: - return &cfg.Metadata } return nil } @@ -193,8 +185,6 @@ func (c *StoredConfig) GetLabels() *map[string]string { switch cfg := c.Configuration.(type) { case api.RestAPI: return cfg.Metadata.Labels - case api.WebSubAPI: - return cfg.Metadata.Labels } return nil } @@ -204,8 +194,6 @@ func (c *StoredConfig) GetAnnotations() *map[string]string { switch cfg := c.Configuration.(type) { case api.RestAPI: return cfg.Metadata.Annotations - case api.WebSubAPI: - return cfg.Metadata.Annotations } return nil } diff --git a/gateway/gateway-controller/pkg/models/stored_config_test.go b/gateway/gateway-controller/pkg/models/stored_config_test.go index b4ca7a93c5..22b8862afa 100644 --- a/gateway/gateway-controller/pkg/models/stored_config_test.go +++ b/gateway/gateway-controller/pkg/models/stored_config_test.go @@ -146,21 +146,6 @@ func TestGetContext_RestAPI_WithVersionPlaceholderMultiple(t *testing.T) { assert.Equal(t, "/v2.0/api/v2.0", ctx) } -func TestGetContext_WebSubAPI_WithVersionPlaceholder(t *testing.T) { - config := &StoredConfig{ - Version: "v1.0", - SourceConfiguration: api.WebSubAPI{ - Spec: api.WebhookAPIData{ - Context: "/events/$version", - }, - }, - } - - ctx, err := config.GetContext() - require.NoError(t, err) - assert.Equal(t, "/events/v1.0", ctx) -} - func TestGetContext_UnsupportedType(t *testing.T) { config := &StoredConfig{ SourceConfiguration: map[string]interface{}{"kind": "unknown"}, diff --git a/gateway/gateway-controller/pkg/policy/builder.go b/gateway/gateway-controller/pkg/policy/builder.go index 41e1c683ff..3fa338d2f1 100644 --- a/gateway/gateway-controller/pkg/policy/builder.go +++ b/gateway/gateway-controller/pkg/policy/builder.go @@ -33,8 +33,20 @@ import ( policyenginev1 "github.com/wso2/api-platform/sdk/core/policyengine" ) +// eventGatewayPolicyChainBuilder is an optional hook, set by an +// event-gateway-controller binary, that builds policy chains for kinds core +// doesn't know about (e.g. WebSubApi). Nil (and skipped) when event-gateway +// support is not compiled in. +var eventGatewayPolicyChainBuilder func(cfg *models.StoredConfig, routerConfig *config.RouterConfig, systemConfig *config.Config, policyDefinitions map[string]models.PolicyDefinition) []policyenginev1.PolicyChain + +// RegisterEventGatewayPolicyChainBuilder registers the event-gateway policy-chain hook. +func RegisterEventGatewayPolicyChainBuilder(fn func(cfg *models.StoredConfig, routerConfig *config.RouterConfig, systemConfig *config.Config, policyDefinitions map[string]models.PolicyDefinition) []policyenginev1.PolicyChain) { + eventGatewayPolicyChainBuilder = fn +} + // DerivePolicyFromAPIConfig derives a policy configuration from an API stored config. -// Handles both RestApi and WebSubApi kinds. This is a shared utility used by: +// Handles RestApi natively; other kinds (e.g. WebSubApi) are delegated to +// eventGatewayPolicyChainBuilder when registered. This is a shared utility used by: // - APIDeploymentService (WebSocket event path) // - APIServer handlers (REST API path) - TODO: Refactor this to use the implementation // - main.go startup (loading existing configs) @@ -61,82 +73,6 @@ func DerivePolicyFromAPIConfig(cfg *models.StoredConfig, routerConfig *config.Ro routes := make([]policyenginev1.PolicyChain, 0) switch cfgTyped := cfg.Configuration.(type) { - case api.WebSubAPI: - apiData := cfgTyped.Spec - var channels map[string]api.WebSubChannel - if apiData.Channels != nil { - channels = *apiData.Channels - } - for chName, ch := range channels { - var finalPolicies []policyenginev1.PolicyInstance - - // Policy execution order: allChannels (on_subscription) -> per-channel policies - // Start with API-level subscription policies - if apiData.AllChannels != nil && apiData.AllChannels.OnSubscription != nil && apiData.AllChannels.OnSubscription.Policies != nil { - finalPolicies = make([]policyenginev1.PolicyInstance, 0, len(*apiData.AllChannels.OnSubscription.Policies)) - for _, p := range *apiData.AllChannels.OnSubscription.Policies { - resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, p.Name, p.Version) - if err != nil { - slog.Error("Failed to resolve policy version for all-channel subscription policy", "policy_name", p.Name, "error", err) - continue - } - finalPolicies = append(finalPolicies, ConvertAPIPolicyToModel(p, policyv1alpha.LevelAPI, versionutil.MajorVersion(resolved))) - } - } - - // Append channel-level on_subscription policies - if ch.OnSubscription != nil && ch.OnSubscription.Policies != nil && len(*ch.OnSubscription.Policies) > 0 { - for _, opPolicy := range *ch.OnSubscription.Policies { - resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, opPolicy.Name, opPolicy.Version) - if err != nil { - slog.Error("Failed to resolve policy version for channel-level policy", "policy_name", opPolicy.Name, "channel_name", chName, "error", err) - continue - } - finalPolicies = append(finalPolicies, ConvertAPIPolicyToModel(opPolicy, policyv1alpha.LevelRoute, versionutil.MajorVersion(resolved))) - } - } - - routeKey := xds.GenerateRouteName("SUB", apiData.Context, apiData.Version, chName, routerConfig.GatewayHost) - props := make(map[string]any) - injectedPolicies := utils.InjectSystemPolicies(finalPolicies, systemConfig, props) - - routes = append(routes, policyenginev1.PolicyChain{ - RouteKey: routeKey, - Policies: injectedPolicies, - }) - - // Build UNSUB (unsubscription) policy chain for this channel - var unsubPolicies []policyenginev1.PolicyInstance - if apiData.AllChannels != nil && apiData.AllChannels.OnUnsubscription != nil && apiData.AllChannels.OnUnsubscription.Policies != nil { - unsubPolicies = make([]policyenginev1.PolicyInstance, 0, len(*apiData.AllChannels.OnUnsubscription.Policies)) - for _, p := range *apiData.AllChannels.OnUnsubscription.Policies { - resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, p.Name, p.Version) - if err != nil { - slog.Error("Failed to resolve policy version for all-channel unsubscription policy", "policy_name", p.Name, "error", err) - continue - } - unsubPolicies = append(unsubPolicies, ConvertAPIPolicyToModel(p, policyv1alpha.LevelAPI, versionutil.MajorVersion(resolved))) - } - } - if ch.OnUnsubscription != nil && ch.OnUnsubscription.Policies != nil && len(*ch.OnUnsubscription.Policies) > 0 { - for _, opPolicy := range *ch.OnUnsubscription.Policies { - resolved, err := config.ResolvePolicyVersion(policyDefinitions, latestVersions, opPolicy.Name, opPolicy.Version) - if err != nil { - slog.Error("Failed to resolve policy version for channel-level unsubscription policy", "policy_name", opPolicy.Name, "channel_name", chName, "error", err) - continue - } - unsubPolicies = append(unsubPolicies, ConvertAPIPolicyToModel(opPolicy, policyv1alpha.LevelRoute, versionutil.MajorVersion(resolved))) - } - } - unsubRouteKey := xds.GenerateRouteName("UNSUB", apiData.Context, apiData.Version, chName, routerConfig.GatewayHost) - unsubProps := make(map[string]any) - injectedUnsubPolicies := utils.InjectSystemPolicies(unsubPolicies, systemConfig, unsubProps) - routes = append(routes, policyenginev1.PolicyChain{ - RouteKey: unsubRouteKey, - Policies: injectedUnsubPolicies, - }) - } - case api.RestAPI: apiData := cfgTyped.Spec for _, op := range apiData.Operations { @@ -196,6 +132,10 @@ func DerivePolicyFromAPIConfig(cfg *models.StoredConfig, routerConfig *config.Ro }) } } + default: + if eventGatewayPolicyChainBuilder != nil { + routes = append(routes, eventGatewayPolicyChainBuilder(cfg, routerConfig, systemConfig, policyDefinitions)...) + } } // If there are no policies at all, return nil diff --git a/gateway/gateway-controller/pkg/policyxds/combined_cache.go b/gateway/gateway-controller/pkg/policyxds/combined_cache.go index ffd2487aaa..d7273a3b05 100644 --- a/gateway/gateway-controller/pkg/policyxds/combined_cache.go +++ b/gateway/gateway-controller/pkg/policyxds/combined_cache.go @@ -29,7 +29,6 @@ import ( "github.com/wso2/api-platform/gateway/gateway-controller/pkg/apikeyxds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/lazyresourcexds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/subscriptionxds" - "github.com/wso2/api-platform/gateway/gateway-controller/pkg/webhooksecretxds" ) // CombinedCache combines policy, API key, lazy resource, subscription, route config, event channel, @@ -178,7 +177,7 @@ func (c *CombinedCache) CreateWatch(request *cache.Request, subscription cache.S delete(c.watchers, watcherID) return nil, fmt.Errorf("create event channel watch: %w", err) } - case webhooksecretxds.WebhookSecretStateTypeURL: + case WebhookSecretStateTypeURL: if c.webhookSecretCache == nil { delete(c.watchers, watcherID) return nil, fmt.Errorf("webhook secret cache is not configured for type %s", request.TypeUrl) @@ -572,7 +571,7 @@ func (c *CombinedCache) CreateDeltaWatch(request *cache.DeltaRequest, subscripti } } } - case webhooksecretxds.WebhookSecretStateTypeURL: + case WebhookSecretStateTypeURL: if c.webhookSecretCache != nil { if deltaWatcher, ok := c.webhookSecretCache.(interface { CreateDeltaWatch(*cache.DeltaRequest, cache.Subscription, chan cache.DeltaResponse) (func(), error) diff --git a/gateway/gateway-controller/pkg/policyxds/server.go b/gateway/gateway-controller/pkg/policyxds/server.go index 5049328ddf..36e05842ec 100644 --- a/gateway/gateway-controller/pkg/policyxds/server.go +++ b/gateway/gateway-controller/pkg/policyxds/server.go @@ -30,7 +30,6 @@ import ( "github.com/wso2/api-platform/gateway/gateway-controller/pkg/apikeyxds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/lazyresourcexds" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/subscriptionxds" - "github.com/wso2/api-platform/gateway/gateway-controller/pkg/webhooksecretxds" core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" @@ -40,6 +39,15 @@ import ( "google.golang.org/grpc/keepalive" ) +// WebhookSecretCacheProvider is the extension point through which an external +// event-gateway-controller binary supplies the xDS cache backing webhook-secret +// (HMAC) resources. Core never implements this interface itself; it is only +// ever satisfied by a webhooksecretxds.SnapshotManager living outside this +// module. +type WebhookSecretCacheProvider interface { + GetCache() cache.Cache +} + // Server is the policy xDS gRPC server type Server struct { grpcServer *grpc.Server @@ -48,7 +56,7 @@ type Server struct { apiKeySnapshotMgr *apikeyxds.APIKeySnapshotManager lazyResourceSnapshotMgr *lazyresourcexds.LazyResourceSnapshotManager subscriptionSnapshotMgr *subscriptionxds.SnapshotManager - webhookSecretSnapshotMgr *webhooksecretxds.SnapshotManager + webhookSecretSnapshotMgr WebhookSecretCacheProvider port int tlsConfig *TLSConfig onFirstConnect chan struct{} @@ -84,7 +92,7 @@ func WithOnFirstConnect(ch chan struct{}) ServerOption { } // NewServer creates a new policy xDS server -func NewServer(snapshotManager *SnapshotManager, apiKeySnapshotMgr *apikeyxds.APIKeySnapshotManager, lazyResourceSnapshotMgr *lazyresourcexds.LazyResourceSnapshotManager, subscriptionSnapshotMgr *subscriptionxds.SnapshotManager, webhookSecretSnapshotMgr *webhooksecretxds.SnapshotManager, port int, logger *slog.Logger, opts ...ServerOption) *Server { +func NewServer(snapshotManager *SnapshotManager, apiKeySnapshotMgr *apikeyxds.APIKeySnapshotManager, lazyResourceSnapshotMgr *lazyresourcexds.LazyResourceSnapshotManager, subscriptionSnapshotMgr *subscriptionxds.SnapshotManager, webhookSecretSnapshotMgr WebhookSecretCacheProvider, port int, logger *slog.Logger, opts ...ServerOption) *Server { s := &Server{ snapshotManager: snapshotManager, apiKeySnapshotMgr: apiKeySnapshotMgr, diff --git a/gateway/gateway-controller/pkg/policyxds/snapshot.go b/gateway/gateway-controller/pkg/policyxds/snapshot.go index b3b0b92b3f..6aa6e336da 100644 --- a/gateway/gateway-controller/pkg/policyxds/snapshot.go +++ b/gateway/gateway-controller/pkg/policyxds/snapshot.go @@ -42,6 +42,12 @@ const ( // EventChannelConfigTypeURL is the custom type URL for event gateway channel configurations EventChannelConfigTypeURL = "api-platform.wso2.org/v1.EventChannelConfig" + + // WebhookSecretStateTypeURL is the custom type URL for webhook-secret (HMAC) + // state snapshots. Must match event-gateway/gateway-controller's + // pkg/webhooksecretxds.WebhookSecretStateTypeURL — core only routes xDS + // watches for this type here; it never produces the resource itself. + WebhookSecretStateTypeURL = "api-platform.wso2.org/v1.WebhookSecretState" ) // SnapshotManager manages xDS snapshots for policy and route configurations. @@ -93,6 +99,12 @@ func (sm *SnapshotManager) SetConfigStore(store *storage.ConfigStore) { sm.configStore = store } +// GetTranslator returns the underlying policy Translator, so an +// event-gateway-controller binary can register its EventChannelTranslator hook. +func (sm *SnapshotManager) GetTranslator() *Translator { + return sm.translator +} + // GetRouteCache returns the route config cache. func (sm *SnapshotManager) GetRouteCache() cache.Cache { return sm.routeCache @@ -148,11 +160,11 @@ func (sm *SnapshotManager) UpdateSnapshot(ctx context.Context) error { sm.routeCache.SetResources(routeById) // Update event channel config cache from WebSubApi configs - if sm.configStore != nil { - eventChannelResources := sm.translator.TranslateWebSubApisToEventChannelConfigs(sm.configStore.GetAllByKind("WebSubApi")) + if sm.configStore != nil && sm.translator.eventChannelHooks != nil { + eventChannelResources := sm.translator.eventChannelHooks.TranslateWebSubApisToEventChannelConfigs(sm.configStore.GetAllByKind("WebSubApi")) // Also translate WebBrokerApi configs - webBrokerResources := sm.translator.TranslateWebBrokerApisToEventChannelConfigs(sm.configStore.GetAllByKind("WebBrokerApi")) + webBrokerResources := sm.translator.eventChannelHooks.TranslateWebBrokerApisToEventChannelConfigs(sm.configStore.GetAllByKind("WebBrokerApi")) // Merge both resource maps for uuid, resource := range webBrokerResources { @@ -196,6 +208,25 @@ func (sm *SnapshotManager) UpdateSnapshot(ctx context.Context) error { // Translator converts RuntimeDeployConfig to xDS resources. type Translator struct { logger *slog.Logger + + // eventChannelHooks is an optional hook, set by an event-gateway-controller + // binary, that translates WebSubApi/WebBrokerApi configs into event-channel + // xDS resources. Nil (and skipped) when event-gateway support is not + // compiled in. + eventChannelHooks EventChannelTranslator +} + +// EventChannelTranslator is the extension point through which an external +// event-gateway-controller binary supplies WebSub/WebBroker event-channel xDS +// translation. Core never implements this interface itself. +type EventChannelTranslator interface { + TranslateWebSubApisToEventChannelConfigs(configs []*models.StoredConfig) map[string]types.Resource + TranslateWebBrokerApisToEventChannelConfigs(configs []*models.StoredConfig) map[string]types.Resource +} + +// SetEventChannelHooks registers the event-gateway event-channel translation hook. +func (t *Translator) SetEventChannelHooks(h EventChannelTranslator) { + t.eventChannelHooks = h } // NewTranslator creates a new policy translator. @@ -396,6 +427,12 @@ func toAnyResource(data map[string]interface{}, typeURL string) (types.Resource, return anyMsg, nil } +// ToAnyResource exposes toAnyResource for use by EventChannelTranslator +// implementations living outside this module. +func ToAnyResource(data map[string]interface{}, typeURL string) (types.Resource, error) { + return toAnyResource(data, typeURL) +} + // slogAdapter adapts slog.Logger to the go-control-plane Logger interface type slogAdapter struct { logger *slog.Logger diff --git a/gateway/gateway-controller/pkg/service/restapi/service.go b/gateway/gateway-controller/pkg/service/restapi/service.go index 5e53f4e0b8..ae8838a24f 100644 --- a/gateway/gateway-controller/pkg/service/restapi/service.go +++ b/gateway/gateway-controller/pkg/service/restapi/service.go @@ -19,13 +19,10 @@ package restapi import ( - "context" "fmt" "log/slog" "net/http" "strings" - "sync" - "sync/atomic" "time" "github.com/wso2/api-platform/common/eventhub" @@ -89,6 +86,17 @@ type RestAPIService struct { logger *slog.Logger eventHub eventhub.EventHub secretResolver funcs.SecretResolver + + // webSubTopicDeregistrar is an optional hook, set by an event-gateway-controller + // binary, that deregisters WebSub hub topics for a deleted WebSubApi config. + // It is nil (and skipped) when event-gateway support is not compiled in. + webSubTopicDeregistrar func(cfg *models.StoredConfig, log *slog.Logger) error +} + +// SetWebSubTopicDeregistrar registers the event-gateway hook invoked from Delete +// when a WebSubApi config is removed. Passing nil (the default) disables it. +func (s *RestAPIService) SetWebSubTopicDeregistrar(fn func(cfg *models.StoredConfig, log *slog.Logger) error) { + s.webSubTopicDeregistrar = fn } // NewRestAPIService creates a new RestAPIService. @@ -412,9 +420,9 @@ func (s *RestAPIService) Delete(params DeleteParams) (*DeleteResult, error) { } // - // WebSub topic deregistration - if cfg.Kind == "WebSubApi" { - if err := s.deregisterWebSubTopics(cfg, log); err != nil { + // WebSub topic deregistration (event-gateway-controller only) + if cfg.Kind == "WebSubApi" && s.webSubTopicDeregistrar != nil { + if err := s.webSubTopicDeregistrar(cfg, log); err != nil { return nil, err } } @@ -534,55 +542,6 @@ func (s *RestAPIService) waitForDeploymentAndPush(configID string, correlationID } } -// deregisterWebSubTopics handles WebSub topic deregistration on delete. -func (s *RestAPIService) deregisterWebSubTopics(cfg *models.StoredConfig, log *slog.Logger) error { - topicsToUnregister := s.deploymentService.GetTopicsForDelete(*cfg) - - var deregErrs int32 - var wg sync.WaitGroup - - if len(topicsToUnregister) > 0 { - wg.Add(1) - go func(list []string) { - defer wg.Done() - log.Info("Starting topic deregistration", slog.Int("total_topics", len(list)), slog.String("api_id", cfg.UUID)) - var childWg sync.WaitGroup - for _, topic := range list { - childWg.Add(1) - go func(topic string) { - defer childWg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.routerConfig.EventGateway.TimeoutSeconds)*time.Second) - defer cancel() - if err := s.deploymentService.UnregisterTopicWithHub(ctx, s.httpClient, topic, s.routerConfig.EventGateway.RouterHost, s.routerConfig.EventGateway.WebSubHubListenerPort, log); err != nil { - log.Error("Failed to deregister topic from WebSubHub", - slog.Any("error", err), - slog.String("topic", topic), - slog.String("api_id", cfg.UUID)) - atomic.AddInt32(&deregErrs, 1) - } else { - log.Info("Successfully deregistered topic from WebSubHub", - slog.String("topic", topic), - slog.String("api_id", cfg.UUID)) - } - }(topic) - } - childWg.Wait() - }(topicsToUnregister) - } - - wg.Wait() - - log.Info("Topic lifecycle operations completed", - slog.String("api_id", cfg.UUID), - slog.Int("deregistered", len(topicsToUnregister)), - slog.Int("deregister_errors", int(deregErrs))) - - if deregErrs > 0 { - return fmt.Errorf("topic lifecycle operations failed") - } - return nil -} - func stringPtr(s string) *string { return &s } diff --git a/gateway/gateway-controller/pkg/storage/memory.go b/gateway/gateway-controller/pkg/storage/memory.go index 92300e6d6b..454898e6d1 100644 --- a/gateway/gateway-controller/pkg/storage/memory.go +++ b/gateway/gateway-controller/pkg/storage/memory.go @@ -23,7 +23,6 @@ import ( "strings" "sync" - api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" ) @@ -36,6 +35,12 @@ type ConfigStore struct { snapVersion int64 // Current xDS snapshot version TopicManager *TopicManager + // webSubTopicUpdater is an optional hook, set by an event-gateway-controller + // binary, that derives WebSub hub topics from a WebSubApi config and syncs + // them into TopicManager. Nil (and skipped) when event-gateway support is + // not compiled in. + webSubTopicUpdater func(cfg *models.StoredConfig, tm *TopicManager) error + // LLM Provider Templates templates map[string]*models.StoredLLMProviderTemplate // Key: template ID templateIdByHandle map[string]string @@ -91,8 +96,8 @@ func (cs *ConfigStore) Add(cfg *models.StoredConfig) error { cs.labelsByAPI[cfg.Handle] = labelsCopy } - if cfg.Kind == "WebSubApi" { - err := cs.updateTopics(cfg) + if cfg.Kind == "WebSubApi" && cs.webSubTopicUpdater != nil { + err := cs.webSubTopicUpdater(cfg, cs.TopicManager) if err != nil { return err } @@ -140,8 +145,8 @@ func (cs *ConfigStore) Update(cfg *models.StoredConfig) error { cs.nameVersion[newKey] = cfg.UUID } - if cfg.Kind == "WebSubApi" { - err := cs.updateTopics(cfg) + if cfg.Kind == "WebSubApi" && cs.webSubTopicUpdater != nil { + err := cs.webSubTopicUpdater(cfg, cs.TopicManager) if err != nil { return err } @@ -175,37 +180,10 @@ func (cs *ConfigStore) Update(cfg *models.StoredConfig) error { return nil } -func (cs *ConfigStore) updateTopics(cfg *models.StoredConfig) error { - webSubCfg, ok := cfg.Configuration.(api.WebSubAPI) - if !ok { - return fmt.Errorf("configuration is not a WebSubAPI") - } - asyncData := webSubCfg.Spec - // Maintaining a topic map to process topics - // Running these inside Add or Delete configs might add extra latency to the API Deployment process - // TODO: Optimize topic management if needed by maintaining a separate topic manager struct - - apiTopicsPerRevision := make(map[string]bool) - var channels map[string]api.WebSubChannel - if asyncData.Channels != nil { - channels = *asyncData.Channels - } - for chName := range channels { - contextWithVersion := strings.ReplaceAll(asyncData.Context, "$version", asyncData.Version) - contextWithVersion = strings.TrimPrefix(contextWithVersion, "/") - contextWithVersion = strings.ReplaceAll(contextWithVersion, "/", "_") - name := strings.TrimPrefix(chName, "/") - modifiedTopic := fmt.Sprintf("%s_%s", contextWithVersion, name) - cs.TopicManager.Add(cfg.UUID, modifiedTopic) - apiTopicsPerRevision[modifiedTopic] = true - } - - for _, topic := range cs.TopicManager.GetAllByConfig(cfg.UUID) { - if _, exists := apiTopicsPerRevision[topic]; !exists { - cs.TopicManager.Remove(cfg.UUID, topic) - } - } - return nil +// SetWebSubTopicUpdater registers the event-gateway hook invoked from Add/Update +// when a WebSubApi config is stored. Passing nil (the default) disables it. +func (cs *ConfigStore) SetWebSubTopicUpdater(fn func(cfg *models.StoredConfig, tm *TopicManager) error) { + cs.webSubTopicUpdater = fn } // Delete removes a configuration from memory diff --git a/gateway/gateway-controller/pkg/storage/sql_store.go b/gateway/gateway-controller/pkg/storage/sql_store.go index 585a157249..dec66da84f 100644 --- a/gateway/gateway-controller/pkg/storage/sql_store.go +++ b/gateway/gateway-controller/pkg/storage/sql_store.go @@ -296,8 +296,21 @@ func kindToResourceTable(kind string) (string, error) { } } +// kindUnmarshalers holds JSON-unmarshaling functions for artifact kinds not +// known to core — registered by an event-gateway-controller binary (e.g. for +// "WebSubApi"/"WebBrokerApi") via RegisterKindUnmarshaler. This mirrors the +// self-registering init() idiom already used by pkg/templateengine/funcs. +var kindUnmarshalers = map[string]func(cfg *models.StoredConfig, jsonData string) error{} + +// RegisterKindUnmarshaler registers a JSON-unmarshaling function for an +// artifact kind not known to core. Intended to be called from an init() in a +// binary that links in support for that kind (e.g. event-gateway-controller). +func RegisterKindUnmarshaler(kind string, fn func(cfg *models.StoredConfig, jsonData string) error) { + kindUnmarshalers[kind] = fn +} + // unmarshalSourceConfig unmarshals JSON into the correct typed struct for the given kind. -// RestApi/WebSubApi rows can populate Configuration directly because the stored +// RestApi rows can populate Configuration directly because the stored // payload is already the deployable shape. LLM provider/proxy rows only restore // SourceConfiguration; their derived RestAPI form is rebuilt later by the // deployment/event-listener layer once templates and policies are available. @@ -310,20 +323,6 @@ func unmarshalSourceConfig(cfg *models.StoredConfig, jsonData string) error { } cfg.SourceConfiguration = config cfg.Configuration = config - case "WebSubApi": - var config api.WebSubAPI - 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 "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 "LlmProviderTemplate": var config api.LLMProviderTemplate if err := json.Unmarshal([]byte(jsonData), &config); err != nil { @@ -352,6 +351,9 @@ func unmarshalSourceConfig(cfg *models.StoredConfig, jsonData string) error { } cfg.SourceConfiguration = config default: + if fn, ok := kindUnmarshalers[cfg.Kind]; ok { + return fn(cfg, jsonData) + } return fmt.Errorf("unknown kind: %s", cfg.Kind) } return nil diff --git a/gateway/gateway-controller/pkg/utils/api_deployment.go b/gateway/gateway-controller/pkg/utils/api_deployment.go index ce101183b6..dca40aab43 100644 --- a/gateway/gateway-controller/pkg/utils/api_deployment.go +++ b/gateway/gateway-controller/pkg/utils/api_deployment.go @@ -159,6 +159,40 @@ func (s *APIDeploymentService) publishEvent(eventType eventhub.EventType, action } } +// KindDeployParser parses raw deployment payload bytes into a typed config for +// a resource kind not known to core (e.g. "WebSubApi"/"WebBrokerApi"), +// returning the parsed config, its handle/kind, and any artifact-id annotation. +type KindDeployParser func(parser *config.Parser, data []byte, contentType string) (parsedConfig any, handle string, kind string, annotationArtifactID string, err error) + +// kindDeployParsers holds KindDeployParser functions for kinds not known to +// core — registered by an event-gateway-controller binary via +// RegisterKindDeployParser. +var kindDeployParsers = map[string]KindDeployParser{} + +// RegisterKindDeployParser registers a deployment-payload parser for a +// resource kind not known to core. Intended to be called from an init() in a +// binary that links in support for that kind (e.g. event-gateway-controller). +func RegisterKindDeployParser(resourceKind string, fn KindDeployParser) { + kindDeployParsers[resourceKind] = fn +} + +// KindConfigValidator validates a rendered configuration of a kind not known +// to core, returning the extracted display name/version and any validation +// errors (empty means valid). +type KindConfigValidator func(cfg any) (apiName, apiVersion string, validationErrors []config.ValidationError) + +// kindConfigValidators holds KindConfigValidator functions for kinds not known +// to core — registered by an event-gateway-controller binary via +// RegisterKindConfigValidator. +var kindConfigValidators = map[string]KindConfigValidator{} + +// RegisterKindConfigValidator registers a config validator for a resource kind +// not known to core. Intended to be called from an init() in a binary that +// links in support for that kind (e.g. event-gateway-controller). +func RegisterKindConfigValidator(resourceKind string, fn KindConfigValidator) { + kindConfigValidators[resourceKind] = fn +} + // DeployAPIConfiguration handles the complete API configuration deployment process // Important: The APIDeploymentResult contains resolved secrets. Do not expose them in responses. func (s *APIDeploymentService) DeployAPIConfiguration(params APIDeploymentParams) (*APIDeploymentResult, error) { @@ -193,24 +227,6 @@ func (s *APIDeploymentService) DeployAPIConfiguration(params APIDeploymentParams // Version) are extracted later from the rendered Configuration so the validator and // downstream logic see resolved template values, not raw template syntax. switch resolvedKind { - case "WebSubApi": - var webSubConfig api.WebSubAPI - if err := s.parser.Parse(params.Data, params.ContentType, &webSubConfig); err != nil { - return nil, fmt.Errorf("failed to parse configuration: %w", err) - } - handle = webSubConfig.Metadata.Name - 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 { @@ -221,7 +237,15 @@ 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\", \"WebSubApi\", or \"WebBrokerApi\"", resolvedKind) + if fn, ok := kindDeployParsers[resolvedKind]; ok { + var err error + parsedConfig, handle, kind, annotationArtifactID, err = fn(s.parser, params.Data, params.ContentType) + if err != nil { + return nil, fmt.Errorf("failed to parse configuration: %w", err) + } + break + } + return nil, fmt.Errorf("unsupported resource kind %q: must be \"RestApi\"", resolvedKind) } // Resolve API ID: explicit param > artifact-id annotation > auto-generate @@ -296,23 +320,6 @@ func (s *APIDeploymentService) DeployAPIConfiguration(params APIDeploymentParams // Validate against the rendered Configuration and extract spec-level identifiers. var apiName, apiVersion string switch c := storedCfg.Configuration.(type) { - case api.WebSubAPI: - apiName = c.Spec.DisplayName - apiVersion = c.Spec.Version - validationErrors := s.validator.Validate(&c) - if len(validationErrors) > 0 { - 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 @@ -322,6 +329,15 @@ func (s *APIDeploymentService) DeployAPIConfiguration(params APIDeploymentParams return nil, &ValidationErrorListError{Errors: validationErrors} } default: + if fn, ok := kindConfigValidators[kind]; ok { + var validationErrors []config.ValidationError + apiName, apiVersion, validationErrors = fn(storedCfg.Configuration) + if len(validationErrors) > 0 { + s.logValidationErrors(params.Logger, apiID, apiName, validationErrors) + return nil, &ValidationErrorListError{Errors: validationErrors} + } + break + } return nil, fmt.Errorf("unexpected configuration type %T after rendering", storedCfg.Configuration) } storedCfg.DisplayName = apiName @@ -492,46 +508,28 @@ func (s *APIDeploymentService) rollbackPersistedAPIConfiguration( return nil } -func (s *APIDeploymentService) GetTopicsForUpdate(apiConfig models.StoredConfig) ([]string, []string) { - topics := s.store.TopicManager.GetAllByConfig(apiConfig.UUID) - topicsToRegister := []string{} - topicsToUnregister := []string{} - apiTopicsPerRevision := make(map[string]bool) - - webSubCfg, ok := apiConfig.Configuration.(api.WebSubAPI) - if !ok { - return topicsToRegister, topicsToUnregister - } - asyncData := webSubCfg.Spec - - var channels map[string]api.WebSubChannel - if asyncData.Channels != nil { - channels = *asyncData.Channels - } - for chName := range channels { - // Remove leading '/' from name, context, version and topic path if present - contextWithVersion := strings.ReplaceAll(asyncData.Context, "$version", asyncData.Version) - contextWithVersion = strings.TrimPrefix(contextWithVersion, "/") - contextWithVersion = strings.ReplaceAll(contextWithVersion, "/", "_") - name := strings.TrimPrefix(chName, "/") - modifiedTopic := fmt.Sprintf("%s_%s", contextWithVersion, name) - apiTopicsPerRevision[modifiedTopic] = true - } - - for _, topic := range topics { - if _, exists := apiTopicsPerRevision[topic]; !exists { - topicsToUnregister = append(topicsToUnregister, topic) - } - } +// KindTopicsForUpdateFunc computes the topic register/unregister diff for a +// resource kind not known to core (e.g. "WebSubApi"), given the shared +// TopicManager and the (rendered) config being deployed. +type KindTopicsForUpdateFunc func(tm *storage.TopicManager, apiConfig models.StoredConfig) (topicsToRegister, topicsToUnregister []string) + +// kindTopicsForUpdateFuncs holds KindTopicsForUpdateFunc functions for kinds +// not known to core — registered by an event-gateway-controller binary via +// RegisterKindTopicsForUpdate. +var kindTopicsForUpdateFuncs = map[string]KindTopicsForUpdateFunc{} + +// RegisterKindTopicsForUpdate registers a topics-for-update diff function for +// a resource kind not known to core. Intended to be called from an init() in +// a binary that links in support for that kind (e.g. event-gateway-controller). +func RegisterKindTopicsForUpdate(resourceKind string, fn KindTopicsForUpdateFunc) { + kindTopicsForUpdateFuncs[resourceKind] = fn +} - for topic := range apiTopicsPerRevision { - if s.store.TopicManager.IsTopicExist(apiConfig.UUID, topic) { - continue - } - topicsToRegister = append(topicsToRegister, topic) +func (s *APIDeploymentService) GetTopicsForUpdate(apiConfig models.StoredConfig) ([]string, []string) { + if fn, ok := kindTopicsForUpdateFuncs[apiConfig.Kind]; ok { + return fn(s.store.TopicManager, apiConfig) } - - return topicsToRegister, topicsToUnregister + return []string{}, []string{} } func (s *APIDeploymentService) logValidationErrors(logger *slog.Logger, apiID string, apiName string, validationErrors []config.ValidationError) { @@ -685,64 +683,37 @@ func resolveVhostSentinels(cfg *any, routerCfg *config.RouterConfig) error { } } *cfg = c - case api.WebSubAPI: - 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 - 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 + default: + if fn, ok := kindVhostSentinelResolvers[fmt.Sprintf("%T", c)]; ok { + resolved, err := fn(c, routerCfg) + if err != nil { + return err } + *cfg = resolved } - *cfg = c } return nil } +// KindVhostSentinelResolver resolves gateway-default vhost sentinels for a +// config type not known to core (e.g. eventgateway.WebSubAPI), returning the +// updated config value. +type KindVhostSentinelResolver func(cfg any, routerCfg *config.RouterConfig) (any, error) + +// kindVhostSentinelResolvers holds KindVhostSentinelResolver functions keyed +// by the dynamic Go type name (fmt.Sprintf("%T", cfg)) of the config value — +// registered by an event-gateway-controller binary via +// RegisterKindVhostSentinelResolver. +var kindVhostSentinelResolvers = map[string]KindVhostSentinelResolver{} + +// RegisterKindVhostSentinelResolver registers a vhost-sentinel resolver for a +// config Go type (given as e.g. "eventgateway.WebSubAPI") not known to core. +// Intended to be called from an init() in a binary that links in support for +// that type (e.g. event-gateway-controller). +func RegisterKindVhostSentinelResolver(goTypeName string, fn KindVhostSentinelResolver) { + kindVhostSentinelResolvers[goTypeName] = fn +} + // annotationValue safely reads a single annotation key from a pointer-to-map. func annotationValue(annotations *map[string]string, key string) string { if annotations == nil { diff --git a/gateway/gateway-controller/pkg/utils/api_deployment_test.go b/gateway/gateway-controller/pkg/utils/api_deployment_test.go index 12200acaaa..8c094dc006 100644 --- a/gateway/gateway-controller/pkg/utils/api_deployment_test.go +++ b/gateway/gateway-controller/pkg/utils/api_deployment_test.go @@ -84,56 +84,6 @@ func TestAPIDeploymentResult(t *testing.T) { assert.True(t, result.IsUpdate) } -func TestGetTopicsForUpdate(t *testing.T) { - store := storage.NewConfigStore() - service := newTestAPIDeploymentService(store, nil, nil, nil, nil) - - t.Run("Empty config returns empty lists", func(t *testing.T) { - // Create a config with invalid spec (will fail parsing) - storedCfg := models.StoredConfig{ - UUID: "0000-test-api-1-0000-000000000000", - Kind: string(api.WebSubAPIKindWebSubApi), - Origin: models.OriginGatewayAPI, - } - // Set up an empty spec that will fail to parse - storedCfg.Configuration = api.WebSubAPI{ - Kind: api.WebSubAPIKindWebSubApi, - Spec: api.WebhookAPIData{}, - } - - toRegister, toUnregister := service.GetTopicsForUpdate(storedCfg) - assert.Empty(t, toRegister) - assert.Empty(t, toUnregister) - }) - - t.Run("Valid WebSub config returns topics", func(t *testing.T) { - webhookData := api.WebhookAPIData{ - DisplayName: "Test WebSub API", - Version: "1.0.0", - Context: "/test/$version", - Channels: &map[string]api.WebSubChannel{ - "/events": {}, - "/notifications": {}, - }, - } - - storedCfg := models.StoredConfig{ - UUID: "0000-websub-api-1-0000-000000000000", - Kind: string(api.WebSubAPIKindWebSubApi), - Origin: models.OriginGatewayAPI, - Configuration: api.WebSubAPI{ - Kind: api.WebSubAPIKindWebSubApi, - Spec: webhookData, - }, - } - - toRegister, toUnregister := service.GetTopicsForUpdate(storedCfg) - // New topics should be registered - assert.NotEmpty(t, toRegister) - assert.Empty(t, toUnregister) - }) -} - func TestGetTopicsForDelete(t *testing.T) { store := storage.NewConfigStore() service := newTestAPIDeploymentService(store, nil, nil, nil, nil) @@ -141,7 +91,7 @@ func TestGetTopicsForDelete(t *testing.T) { t.Run("Returns topics from topic manager", func(t *testing.T) { storedCfg := models.StoredConfig{ UUID: "0000-test-api-1-0000-000000000000", - Kind: string(api.WebSubAPIKindWebSubApi), + Kind: "WebSubApi", Origin: models.OriginGatewayAPI, } @@ -158,7 +108,7 @@ func TestGetTopicsForDelete(t *testing.T) { t.Run("Returns empty for non-existent config", func(t *testing.T) { storedCfg := models.StoredConfig{ UUID: "0000-non-existent-api-0000-000000000000", - Kind: string(api.WebSubAPIKindWebSubApi), + Kind: "WebSubApi", Origin: models.OriginGatewayAPI, } @@ -565,7 +515,9 @@ spec: assert.ErrorAs(t, err, &validationErr) }) - t.Run("Infers WebSubApi kind from payload", func(t *testing.T) { + t.Run("Inferred WebSubApi kind fails without a registered kind parser", func(t *testing.T) { + // WebSubApi support is only available when an event-gateway-controller + // binary registers a KindDeployParser for it; core alone rejects it. yamlData := ` apiVersion: gateway.api-platform.wso2.com/v1 kind: WebSubApi @@ -586,8 +538,7 @@ spec: _, err := service.DeployAPIConfiguration(params) assert.Error(t, err) - var validationErr *ValidationErrorListError - assert.ErrorAs(t, err, &validationErr) + assert.Contains(t, err.Error(), "unsupported resource kind") }) } @@ -871,13 +822,7 @@ func TestSaveOrUpdateConfig_StaleEvent(t *testing.T) { func TestSendTopicRequestToHub_RetryLogic(t *testing.T) { store := storage.NewConfigStore() - routerConfig := &config.RouterConfig{ - EventGateway: config.EventGatewayConfig{ - RouterHost: "localhost", - WebSubHubListenerPort: 8084, - TimeoutSeconds: 1, - }, - } + routerConfig := &config.RouterConfig{} service := newTestAPIDeploymentService(store, nil, nil, nil, routerConfig) logger := slog.New(slog.NewTextHandler(io.Discard, nil)) @@ -910,13 +855,7 @@ func TestSendTopicRequestToHub_RetryLogic(t *testing.T) { func TestRegisterAndUnregisterTopicWithHub(t *testing.T) { store := storage.NewConfigStore() - routerConfig := &config.RouterConfig{ - EventGateway: config.EventGatewayConfig{ - RouterHost: "localhost", - WebSubHubListenerPort: 8084, - TimeoutSeconds: 1, - }, - } + routerConfig := &config.RouterConfig{} service := newTestAPIDeploymentService(store, nil, nil, nil, routerConfig) logger := slog.New(slog.NewTextHandler(io.Discard, nil)) @@ -1038,90 +977,6 @@ func TestResolveVhostSentinels_NilVhostsNoSandboxDefault(t *testing.T) { assert.Nil(t, resolved.Vhosts.Sandbox, "sandbox should remain nil when no sandbox default configured") } -func TestResolveVhostSentinels_WebSubApi_NilVhostsPopulatesDefaults(t *testing.T) { - routerCfg := &config.RouterConfig{ - VHosts: config.VHostsConfig{ - Main: config.VHostEntry{Default: "*.wso2.com"}, - Sandbox: config.VHostEntry{Default: "*-sandbox.wso2.com"}, - }, - } - - var cfg any = api.WebSubAPI{ - Kind: api.WebSubAPIKindWebSubApi, - Spec: api.WebhookAPIData{Vhosts: nil}, - } - - require.NoError(t, resolveVhostSentinels(&cfg, routerCfg)) - - resolved := cfg.(api.WebSubAPI).Spec - require.NotNil(t, resolved.Vhosts, "nil vhosts should be populated with defaults") - assert.Equal(t, "*.wso2.com", resolved.Vhosts.Main) - require.NotNil(t, resolved.Vhosts.Sandbox) - assert.Equal(t, "*-sandbox.wso2.com", *resolved.Vhosts.Sandbox) -} - -func TestResolveVhostSentinels_WebSubApi(t *testing.T) { - sandbox := constants.VHostGatewayDefault - routerCfg := &config.RouterConfig{ - VHosts: config.VHostsConfig{ - Main: config.VHostEntry{Default: "*.wso2.com"}, - Sandbox: config.VHostEntry{Default: "*-sandbox.wso2.com"}, - }, - } - - var cfg any = api.WebSubAPI{ - Kind: api.WebSubAPIKindWebSubApi, - Spec: api.WebhookAPIData{ - Vhosts: &struct { - Main string `json:"main" yaml:"main"` - Sandbox *string `json:"sandbox,omitempty" yaml:"sandbox,omitempty"` - }{ - Main: constants.VHostGatewayDefault, - Sandbox: &sandbox, - }, - }, - } - - require.NoError(t, resolveVhostSentinels(&cfg, routerCfg)) - - resolved := cfg.(api.WebSubAPI).Spec - require.NotNil(t, resolved.Vhosts) - assert.Equal(t, "*.wso2.com", resolved.Vhosts.Main) - require.NotNil(t, resolved.Vhosts.Sandbox) - assert.Equal(t, "*-sandbox.wso2.com", *resolved.Vhosts.Sandbox) -} - -func TestResolveVhostSentinels_WebSubApi_ExplicitValues(t *testing.T) { - sandboxValue := "custom-sandbox.example.com" - routerCfg := &config.RouterConfig{ - VHosts: config.VHostsConfig{ - Main: config.VHostEntry{Default: "*.wso2.com"}, - Sandbox: config.VHostEntry{Default: "*-sandbox.wso2.com"}, - }, - } - - var cfg any = api.WebSubAPI{ - Kind: api.WebSubAPIKindWebSubApi, - Spec: api.WebhookAPIData{ - Vhosts: &struct { - Main string `json:"main" yaml:"main"` - Sandbox *string `json:"sandbox,omitempty" yaml:"sandbox,omitempty"` - }{ - Main: "custom.example.com", - Sandbox: &sandboxValue, - }, - }, - } - - require.NoError(t, resolveVhostSentinels(&cfg, routerCfg)) - - resolved := cfg.(api.WebSubAPI).Spec - require.NotNil(t, resolved.Vhosts) - assert.Equal(t, "custom.example.com", resolved.Vhosts.Main) - require.NotNil(t, resolved.Vhosts.Sandbox) - assert.Equal(t, "custom-sandbox.example.com", *resolved.Vhosts.Sandbox) -} - func TestResolveVhostSentinels_NilCfgNoOp(t *testing.T) { routerCfg := &config.RouterConfig{} require.NoError(t, resolveVhostSentinels(nil, routerCfg)) // should not panic diff --git a/gateway/gateway-controller/pkg/utils/api_key.go b/gateway/gateway-controller/pkg/utils/api_key.go index e5a04994e9..2ee56da48f 100644 --- a/gateway/gateway-controller/pkg/utils/api_key.go +++ b/gateway/gateway-controller/pkg/utils/api_key.go @@ -379,23 +379,31 @@ func extractConfigDisplayNameVersion(kind string, configuration any) (string, st return "", "", fmt.Errorf("configuration is not a LLMProviderConfiguration (kind: %s)", kind) } return providerCfg.Spec.DisplayName, providerCfg.Spec.Version, nil - case models.KindWebSubApi: - webSubCfg, ok := configuration.(api.WebSubAPI) - if !ok { - return "", "", fmt.Errorf("configuration is not a WebSubAPI (kind: %s)", kind) - } - return webSubCfg.Spec.DisplayName, webSubCfg.Spec.Version, nil - case models.KindWebBrokerApi: - webBrokerCfg, ok := configuration.(api.WebBrokerApi) - if !ok { - return "", "", fmt.Errorf("configuration is not a WebBrokerApi (kind: %s)", kind) - } - return webBrokerCfg.Spec.DisplayName, webBrokerCfg.Spec.Version, nil default: + if fn, ok := kindDisplayNameVersionExtractors[kind]; ok { + return fn(configuration) + } return "", "", fmt.Errorf("unsupported kind for API key operation: '%s'", kind) } } +// KindDisplayNameVersionExtractor extracts DisplayName/Version from a +// configuration of a kind not known to core (e.g. "WebSubApi"). +type KindDisplayNameVersionExtractor func(configuration any) (displayName, version string, err error) + +// kindDisplayNameVersionExtractors holds KindDisplayNameVersionExtractor +// functions for kinds not known to core — registered by an +// event-gateway-controller binary via RegisterKindDisplayNameVersionExtractor. +var kindDisplayNameVersionExtractors = map[string]KindDisplayNameVersionExtractor{} + +// RegisterKindDisplayNameVersionExtractor registers a display-name/version +// extractor for a resource kind not known to core. Intended to be called from +// an init() in a binary that links in support for that kind (e.g. +// event-gateway-controller). +func RegisterKindDisplayNameVersionExtractor(resourceKind string, fn KindDisplayNameVersionExtractor) { + kindDisplayNameVersionExtractors[resourceKind] = fn +} + // RevokeAPIKey handles the API key revocation process // TODO: checks if the index created in policy engine is removed func (s *APIKeyService) RevokeAPIKey(params APIKeyRevocationParams) (*APIKeyRevocationResult, error) { diff --git a/gateway/gateway-controller/pkg/utils/helpers.go b/gateway/gateway-controller/pkg/utils/helpers.go index 23331e2fb6..65db2e0b3f 100644 --- a/gateway/gateway-controller/pkg/utils/helpers.go +++ b/gateway/gateway-controller/pkg/utils/helpers.go @@ -14,8 +14,6 @@ func ExtractNameVersion(cfg any) (string, string, error) { switch c := cfg.(type) { case api.RestAPI: return c.Spec.DisplayName, c.Spec.Version, nil - case api.WebSubAPI: - return c.Spec.DisplayName, c.Spec.Version, nil default: return "", "", fmt.Errorf("unsupported api config type: %T", cfg) } diff --git a/gateway/gateway-controller/pkg/utils/llm_provider_transformer_test.go b/gateway/gateway-controller/pkg/utils/llm_provider_transformer_test.go index 69acb62bd6..5c219a3e19 100644 --- a/gateway/gateway-controller/pkg/utils/llm_provider_transformer_test.go +++ b/gateway/gateway-controller/pkg/utils/llm_provider_transformer_test.go @@ -41,11 +41,6 @@ func stringPtr(s string) *string { // loadDummyConfig creates a dummy router configuration func loadDummyConfig() config.RouterConfig { return config.RouterConfig{ - EventGateway: config.EventGatewayConfig{ - Enabled: true, - WebSubHubURL: "http://host.docker.internal", - WebSubHubPort: 9098, - }, AccessLogs: config.AccessLogsConfig{ Enabled: true, Format: "json", diff --git a/gateway/gateway-controller/pkg/xds/eventgateway_hooks.go b/gateway/gateway-controller/pkg/xds/eventgateway_hooks.go new file mode 100644 index 0000000000..46a79f27f2 --- /dev/null +++ b/gateway/gateway-controller/pkg/xds/eventgateway_hooks.go @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, 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 xds + +import ( + "log/slog" + "net/url" + "time" + + accesslog "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" + cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/config" + "github.com/wso2/api-platform/gateway/gateway-controller/pkg/models" +) + +// EventGatewayXDSHooks is the extension point through which an external +// event-gateway-controller binary supplies WebSub-specific xDS translation +// logic. Core never implements this interface itself; it is only ever +// satisfied by code living outside this module. See SetEventGatewayXDSHooks. +type EventGatewayXDSHooks interface { + // BuildHubResources returns any extra clusters/listeners that should be + // added to the shared xDS snapshot to support WebSubHub connectivity + // (e.g. a dynamic-forward-proxy cluster and internal listener(s)). + // httpsEnabled mirrors the core HTTPS-listener toggle so the hub + // listener(s) can be built consistently with the rest of the snapshot. + // Returning (nil, nil, nil) means "nothing to add". + BuildHubResources(t *Translator, httpsEnabled bool) (clusters []*cluster.Cluster, listeners []*listener.Listener, err error) + + // TranslateWebSubAPI builds routes/clusters for a single WebSubApi + // StoredConfig — the WebSubApi-kind equivalent of translateAPIConfig. + TranslateWebSubAPI(t *Translator, cfg *models.StoredConfig, allConfigs []*models.StoredConfig) ([]*route.Route, []*cluster.Cluster, error) +} + +// SetEventGatewayXDSHooks registers the event-gateway xDS extension. Passing +// nil (the default) means this binary has no event-gateway support compiled +// in, and any WebSubApi-kind config will fail translation with a clear error. +func (t *Translator) SetEventGatewayXDSHooks(h EventGatewayXDSHooks) { + t.eventGatewayHooks = h +} + +// The following exported accessors/wrappers exist solely so that an +// EventGatewayXDSHooks implementation living outside this module can reuse +// the same generic route/cluster-building primitives every other kind uses, +// without duplicating them. + +// Logger returns the translator's logger. +func (t *Translator) Logger() *slog.Logger { + return t.logger +} + +// RouterConfig returns the translator's router configuration. +func (t *Translator) RouterConfig() *config.RouterConfig { + return t.routerConfig +} + +// Config returns the translator's full system configuration. +func (t *Translator) Config() *config.Config { + return t.config +} + +// CreateRoute exposes createRoute for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateRoute(apiId, apiName, apiVersion, context, method, path, clusterName, + upstreamPath string, vhost string, apiKind string, templateHandle string, providerName string, hostRewrite *api.UpstreamHostRewrite, projectID string, timeoutCfg *resolvedTimeout, useClusterHeader bool, upstreamDefPaths map[string]string) *route.Route { + return t.createRoute(apiId, apiName, apiVersion, context, method, path, clusterName, + upstreamPath, vhost, apiKind, templateHandle, providerName, hostRewrite, projectID, timeoutCfg, useClusterHeader, upstreamDefPaths) +} + +// CreateRoutePerTopic exposes createRoutePerTopic for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateRoutePerTopic(apiId, apiName, apiVersion, context, method, channelName, clusterName, vhost, apiKind, projectID string) *route.Route { + return t.createRoutePerTopic(apiId, apiName, apiVersion, context, method, channelName, clusterName, vhost, apiKind, projectID) +} + +// CreateCluster exposes createCluster for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateCluster(name string, upstreamURL *url.URL, upstreamCerts map[string][]byte, connectTimeout *time.Duration) *cluster.Cluster { + return t.createCluster(name, upstreamURL, upstreamCerts, connectTimeout) +} + +// ExtractTemplateHandle exposes extractTemplateHandle for use by EventGatewayXDSHooks implementations. +func (t *Translator) ExtractTemplateHandle(cfg *models.StoredConfig, allConfigs []*models.StoredConfig) string { + return t.extractTemplateHandle(cfg, allConfigs) +} + +// ExtractProviderName exposes extractProviderName for use by EventGatewayXDSHooks implementations. +func (t *Translator) ExtractProviderName(cfg *models.StoredConfig, allConfigs []*models.StoredConfig) string { + return t.extractProviderName(cfg, allConfigs) +} + +// ExtractProjectIDFromConfig exposes extractProjectIDFromConfig for use by EventGatewayXDSHooks implementations. +func ExtractProjectIDFromConfig(cfg *models.StoredConfig) string { + return extractProjectIDFromConfig(cfg) +} + +// EnvoyOriginalPathHeader exposes the envoyOriginalPathHeader constant for use +// by EventGatewayXDSHooks implementations building their own virtual hosts/routes. +const EnvoyOriginalPathHeader = envoyOriginalPathHeader + +// CreateExtProcFilter exposes createExtProcFilter for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateExtProcFilter() (*hcm.HttpFilter, error) { + return t.createExtProcFilter() +} + +// CreateLuaFilter exposes createLuaFilter for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateLuaFilter() (*hcm.HttpFilter, error) { + return t.createLuaFilter() +} + +// CreateAccessLogConfig exposes createAccessLogConfig for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateAccessLogConfig() ([]*accesslog.AccessLog, error) { + return t.createAccessLogConfig() +} + +// CreateTracingConfig exposes createTracingConfig for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateTracingConfig() (*hcm.HttpConnectionManager_Tracing, error) { + return t.createTracingConfig() +} + +// CreateDownstreamTLSContext exposes createDownstreamTLSContext for use by EventGatewayXDSHooks implementations. +func (t *Translator) CreateDownstreamTLSContext() (*tlsv3.DownstreamTlsContext, error) { + return t.createDownstreamTLSContext() +} diff --git a/gateway/gateway-controller/pkg/xds/translator.go b/gateway/gateway-controller/pkg/xds/translator.go index abcb475f7c..6533203263 100644 --- a/gateway/gateway-controller/pkg/xds/translator.go +++ b/gateway/gateway-controller/pkg/xds/translator.go @@ -48,9 +48,6 @@ import ( tracev3 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v3" fileaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3" grpc_accesslogv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" - dfpcluster "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3" - common_dfp "github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3" - dfpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3" extproc "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3" luav3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3" router "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" @@ -92,11 +89,12 @@ func checkedUInt32FromPositiveInt(fieldName string, value int) (uint32, error) { // Translator converts API configurations to Envoy xDS resources type Translator struct { - logger *slog.Logger - routerConfig *config.RouterConfig - certStore *certstore.CertStore - config *config.Config - transformers map[string]models.ConfigTransformer // kind → transformer (optional) + logger *slog.Logger + routerConfig *config.RouterConfig + certStore *certstore.CertStore + config *config.Config + transformers map[string]models.ConfigTransformer // kind → transformer (optional) + eventGatewayHooks EventGatewayXDSHooks // optional, set by an event-gateway-controller binary } // resolvedTimeout represents parsed timeout values for an upstream. @@ -679,7 +677,11 @@ func (t *Translator) TranslateConfigs( // Legacy path: direct translation from StoredConfig (WebSubApi, or fallback) if routesList == nil { if cfg.Kind == "WebSubApi" { - routesList, clusterList, err = t.translateAsyncAPIConfig(cfg, configs) + if t.eventGatewayHooks == nil { + err = fmt.Errorf("WebSubApi configured but event-gateway support is not compiled into this binary") + } else { + routesList, clusterList, err = t.eventGatewayHooks.TranslateWebSubAPI(t, cfg, configs) + } } else { routesList, clusterList, err = t.translateAPIConfig(cfg, configs) } @@ -833,50 +835,16 @@ func (t *Translator) TranslateConfigs( clusters = append(clusters, alsCluster) } - if t.routerConfig.EventGateway.Enabled { - // Add dynamic forward proxy cluster for WebSubHub - dynamicForwardProxyCluster := t.createDynamicForwardProxyCluster() - if dynamicForwardProxyCluster == nil { - return nil, fmt.Errorf("failed to create dynamic forward proxy cluster") - } - clusters = append(clusters, dynamicForwardProxyCluster) - dynamicProxyListener, err := t.createDynamicFwdListenerForWebSubHub(t.routerConfig.HTTPSEnabled) - if err != nil { - return nil, fmt.Errorf("failed to create WebSub listener: %w", err) - } - listeners = append(listeners, dynamicProxyListener) - - parsedURL, err := url.Parse(t.routerConfig.EventGateway.WebSubHubURL) + if t.eventGatewayHooks != nil { + hubClusters, hubListeners, err := t.eventGatewayHooks.BuildHubResources(t, t.routerConfig.HTTPSEnabled) if err != nil { - return nil, fmt.Errorf("invalid upstream URL: %w", err) + return nil, fmt.Errorf("failed to build event-gateway hub resources: %w", err) } - if parsedURL.Port() == "" { - parsedURL.Host = fmt.Sprintf("%s:%d", parsedURL.Hostname(), t.routerConfig.EventGateway.WebSubHubPort) + for _, c := range hubClusters { + clusters = append(clusters, c) } - if parsedURL.Scheme == "" { - parsedURL.Scheme = "http" - } - websubhubCluster := t.createCluster(constants.WEBSUBHUB_INTERNAL_CLUSTER_NAME, parsedURL, nil, nil) - clusters = append(clusters, websubhubCluster) - websubInternalListener, err := t.createInternalListenerForWebSubHub(false) - if err != nil { - return nil, fmt.Errorf("failed to create WebSub internal listener: %w", err) - } - listeners = append(listeners, websubInternalListener) - // Create HTTPS listener for WebSubHub communication if enabled - if t.routerConfig.HTTPSEnabled { - log.Info("HTTPS is enabled, creating HTTPS listener", - slog.Int("https_port", t.routerConfig.HTTPSPort)) - httpsListener, err := t.createInternalListenerForWebSubHub(true) - if err != nil { - log.Error("Failed to create HTTPS listener", slog.Any("error", err)) - return nil, fmt.Errorf("failed to create HTTPS listener: %w", err) - } - log.Info("HTTPS listener created successfully", - slog.String("listener_name", httpsListener.GetName())) - listeners = append(listeners, httpsListener) - } else { - log.Info("HTTPS is disabled, skipping HTTPS listener creation") + for _, l := range hubListeners { + listeners = append(listeners, l) } } @@ -963,63 +931,6 @@ func (t *Translator) getVHostDomains(effectiveVHost string) []string { return appendDomainPatterns(out, effectiveVHost) } -// translateAsyncAPIConfig translates a single API configuration -func (t *Translator) translateAsyncAPIConfig(cfg *models.StoredConfig, allConfigs []*models.StoredConfig) ([]*route.Route, []*cluster.Cluster, error) { - webSubCfg, ok := cfg.Configuration.(api.WebSubAPI) - if !ok { - return nil, nil, fmt.Errorf("configuration is not a WebSubAPI") - } - apiData := webSubCfg.Spec - - clusters := []*cluster.Cluster{} - - mainClusterName := constants.WEBSUBHUB_INTERNAL_CLUSTER_NAME - parsedMainURL, err := url.Parse(t.routerConfig.EventGateway.WebSubHubURL) - if err != nil { - return nil, nil, fmt.Errorf("invalid upstream URL: %w", err) - } - if parsedMainURL.Path == "" { - parsedMainURL.Path = constants.WEBSUB_PATH - } - - // Create routes for each operation (default to main cluster) - routesList := make([]*route.Route, 0) - mainRoutesList := make([]*route.Route, 0) - - // Determine effective vhosts (fallback to global router defaults when not provided) - effectiveMainVHost := t.config.Router.VHosts.Main.Default - if apiData.Vhosts != nil { - if strings.TrimSpace(apiData.Vhosts.Main) != "" { - effectiveMainVHost = apiData.Vhosts.Main - } - } - apiProjectID := extractProjectIDFromConfig(cfg) - - if apiData.Channels != nil { - for chName := range *apiData.Channels { - if !strings.HasPrefix(chName, "/") { - chName = "/" + chName - } - // WebSub hub channels are always SUB; use mainClusterName by default - r := t.createRoutePerTopic(cfg.UUID, apiData.DisplayName, apiData.Version, apiData.Context, "SUB", chName, - mainClusterName, effectiveMainVHost, cfg.Kind, apiProjectID) - mainRoutesList = append(mainRoutesList, r) - // Also create UNSUB route for unsubscription policies - rUnsub := t.createRoutePerTopic(cfg.UUID, apiData.DisplayName, apiData.Version, apiData.Context, "UNSUB", chName, - mainClusterName, effectiveMainVHost, cfg.Kind, apiProjectID) - mainRoutesList = append(mainRoutesList, rUnsub) - } - } - // Extract template handle and provider name for LLM provider/proxy scenarios - templateHandle := t.extractTemplateHandle(cfg, allConfigs) - providerName := t.extractProviderName(cfg, allConfigs) - r := t.createRoute(cfg.UUID, apiData.DisplayName, apiData.Version, apiData.Context, "POST", constants.WEBSUB_PATH, mainClusterName, "/", effectiveMainVHost, cfg.Kind, templateHandle, providerName, nil, apiProjectID, nil, false, nil) - routesList = append(routesList, mainRoutesList...) - routesList = append(routesList, r) - - return routesList, clusters, nil -} - // translateAPIConfig translates a single API configuration func (t *Translator) translateAPIConfig(cfg *models.StoredConfig, allConfigs []*models.StoredConfig) ([]*route.Route, []*cluster.Cluster, error) { restCfg, ok := cfg.Configuration.(api.RestAPI) @@ -1420,433 +1331,6 @@ func (t *Translator) createListener(virtualHosts []*route.VirtualHost, isHTTPS b }, routeConfig, nil } -func (t *Translator) createInternalListenerForWebSubHub(isHTTPS bool) (*listener.Listener, error) { - // Reverse proxy listener: exactly one route /websubhub/operations rewritten to /hub - // This allows clients to call /websubhub/operations and internally reach /hub on upstream. - - routeConfig := &route.RouteConfiguration{ - Name: "websubhub-internal-route", - VirtualHosts: []*route.VirtualHost{{ - Name: "WEBSUBHUB_INTERNAL_VHOST", - Domains: []string{"*"}, - Routes: []*route.Route{{ - Match: &route.RouteMatch{PathSpecifier: &route.RouteMatch_Path{Path: "/websubhub/operations"}}, - Action: &route.Route_Route{Route: &route.RouteAction{ - ClusterSpecifier: &route.RouteAction_Cluster{Cluster: WebSubHubInternalClusterName}, - Timeout: durationpb.New(30 * time.Second), - PrefixRewrite: "/hub", // rewrite path - }}, - }}, - }}, - } - - // Create router filter with typed config - routerConfig := &router.Router{} - routerAny, err := anypb.New(routerConfig) - if err != nil { - return nil, fmt.Errorf("failed to create router config: %w", err) - } - - // Build HTTP filters chain - httpFilters := make([]*hcm.HttpFilter, 0) - - // Add ext_proc filter for policy engine - extProcFilter, err := t.createExtProcFilter() - if err != nil { - return nil, fmt.Errorf("failed to create ext_proc filter: %w", err) - } - httpFilters = append(httpFilters, extProcFilter) - - luaFilter, err := t.createLuaFilter() - if err != nil { - return nil, fmt.Errorf("failed to create lua filter: %w", err) - } - httpFilters = append(httpFilters, luaFilter) - - // Add router filter (must be last) - httpFilters = append(httpFilters, &hcm.HttpFilter{ - Name: wellknown.Router, - ConfigType: &hcm.HttpFilter_TypedConfig{ - TypedConfig: routerAny, - }, - }) - - // Create HTTP connection manager - manager := &hcm.HttpConnectionManager{ - CodecType: hcm.HttpConnectionManager_AUTO, - StatPrefix: "http", - GenerateRequestId: wrapperspb.Bool(true), - RouteSpecifier: &hcm.HttpConnectionManager_RouteConfig{ - RouteConfig: routeConfig, - }, - HttpFilters: httpFilters, - } - - // Add access logs if enabled - if t.routerConfig.AccessLogs.Enabled { - accessLogs, err := t.createAccessLogConfig() - if err != nil { - return nil, fmt.Errorf("failed to create access log config: %w", err) - } - manager.AccessLog = accessLogs - } - - // Add tracing if enabled - tracingConfig, err := t.createTracingConfig() - if err != nil { - return nil, fmt.Errorf("failed to create tracing config: %w", err) - } - if tracingConfig != nil { - manager.Tracing = tracingConfig - } - - pbst, err := anypb.New(manager) - if err != nil { - return nil, err - } - - // Determine listener name and port based on protocol - // TODO: Use config values for port - var listenerName string - var port uint32 - if isHTTPS { - listenerName = fmt.Sprintf("listener_https_%d", constants.WEBSUB_HUB_INTERNAL_HTTPS_PORT) - port = uint32(constants.WEBSUB_HUB_INTERNAL_HTTPS_PORT) - } else { - listenerName = fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_INTERNAL_HTTP_PORT) - port = uint32(constants.WEBSUB_HUB_INTERNAL_HTTP_PORT) - } - - // Create filter chain - filterChain := &listener.FilterChain{ - Filters: []*listener.Filter{{ - Name: wellknown.HTTPConnectionManager, - ConfigType: &listener.Filter_TypedConfig{ - TypedConfig: pbst, - }, - }}, - } - - // Add TLS configuration if HTTPS - if isHTTPS { - tlsContext, err := t.createDownstreamTLSContext() - if err != nil { - return nil, fmt.Errorf("failed to create downstream TLS context: %w", err) - } - - tlsContextAny, err := anypb.New(tlsContext) - if err != nil { - return nil, fmt.Errorf("failed to marshal downstream TLS context: %w", err) - } - - filterChain.TransportSocket = &core.TransportSocket{ - Name: "envoy.transport_sockets.tls", - ConfigType: &core.TransportSocket_TypedConfig{ - TypedConfig: tlsContextAny, - }, - } - } - - return &listener.Listener{ - Name: listenerName, - Address: &core.Address{ - Address: &core.Address_SocketAddress{ - SocketAddress: &core.SocketAddress{ - Protocol: core.SocketAddress_TCP, - Address: "0.0.0.0", - PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: port, - }, - }, - }, - }, - FilterChains: []*listener.FilterChain{filterChain}, - }, nil - - // routeConfig := &route.RouteConfiguration{ - // Name: "websubhub-internal-route", - // VirtualHosts: []*route.VirtualHost{{ - // Name: "WEBSUBHUB_INTERNAL_VHOST", - // Domains: []string{"*"}, - // Routes: []*route.Route{{ - // Match: &route.RouteMatch{PathSpecifier: &route.RouteMatch_Path{Path: "/websubhub/operations"}}, - // Action: &route.Route_Route{Route: &route.RouteAction{ - // ClusterSpecifier: &route.RouteAction_Cluster{Cluster: WebSubHubInternalClusterName}, - // Timeout: durationpb.New(30 * time.Second), - // PrefixRewrite: "/hub", // rewrite path - // }}, - // }}, - // }}, - // } - - // // Router filter - // routerCfg := &router.Router{} - // routerAny, err := anypb.New(routerCfg) - // if err != nil { - // return nil, fmt.Errorf("failed to marshal router config: %w", err) - // } - - // // HttpConnectionManager for port 8083 - // hcmCfg := &hcm.HttpConnectionManager{ - // StatPrefix: "websubhub_internal_8083", - // CodecType: hcm.HttpConnectionManager_AUTO, - // GenerateRequestId: wrapperspb.Bool(true), - // RouteSpecifier: &hcm.HttpConnectionManager_RouteConfig{RouteConfig: routeConfig}, - // HttpFilters: []*hcm.HttpFilter{ - // { - // Name: wellknown.Router, - // ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: routerAny}, - // }, - // }, - // } - - // // Attach access logs if enabled - // if t.routerConfig.AccessLogs.Enabled { - // accessLogs, err := t.createAccessLogConfig() - // if err != nil { - // return nil, fmt.Errorf("failed to create access log config: %w", err) - // } - // hcmCfg.AccessLog = accessLogs - // } - - // // Add tracing if enabled - // tracingCfg, err := t.createTracingConfig() - // if err != nil { - // return nil, fmt.Errorf("failed to create tracing config: %w", err) - // } - // if tracingCfg != nil { - // hcmCfg.Tracing = tracingCfg - // } - - // hcmAny, err := anypb.New(hcmCfg) - // if err != nil { - // return nil, fmt.Errorf("failed to marshal http connection manager: %w", err) - // } - - // return &listener.Listener{ - // Name: "websubhub-internal-8083", - // Address: &core.Address{Address: &core.Address_SocketAddress{SocketAddress: &core.SocketAddress{ - // Protocol: core.SocketAddress_TCP, - // Address: "0.0.0.0", - // PortSpecifier: &core.SocketAddress_PortValue{PortValue: 8083}, - // }}}, - // FilterChains: []*listener.FilterChain{{ - // Filters: []*listener.Filter{{ - // Name: wellknown.HTTPConnectionManager, - // ConfigType: &listener.Filter_TypedConfig{TypedConfig: hcmAny}, - // }}, - // }}, - // }, nil -} - -// createDynamicFwdListenerForWebSubHub creates an Envoy listener with access logging -func (t *Translator) createDynamicFwdListenerForWebSubHub(isHTTPS bool) (*listener.Listener, error) { - // Build the route configuration for dynamic forward proxy listener - // We ignore the passed virtualHosts here and construct the required one matching the sample. - dynamicForwardProxyRouteConfig := &route.RouteConfiguration{ - Name: "dynamic-forward-proxy-routing", - VirtualHosts: []*route.VirtualHost{{ - Name: "DYNAMIXC_FORWARD_PROXY_VHOST_WEBSUBHUB", - Domains: []string{t.routerConfig.EventGateway.WebSubHubURL}, // this should be websubhub domains - // This route never rewrites :path, so unlike the main API vhosts (see the - // matching comment in the main virtual host construction) Envoy never - // re-populates x-envoy-original-path here; stripping any client-supplied - // value still leaves it correctly absent for collector.ignore_path_prefixes. - RequestHeadersToRemove: []string{envoyOriginalPathHeader}, - Routes: []*route.Route{{ - Match: &route.RouteMatch{PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/"}}, - Action: &route.Route_Route{Route: &route.RouteAction{ - ClusterSpecifier: &route.RouteAction_Cluster{Cluster: DynamicForwardProxyClusterName}, - Timeout: durationpb.New(30 * time.Second), - RetryPolicy: &route.RetryPolicy{ - RetryOn: "5xx,reset,connect-failure,refused-stream", - NumRetries: wrapperspb.UInt32(1), - }, - }}, - }}, - }}, - } - // Build HTTP filters chain - httpFilters := make([]*hcm.HttpFilter, 0) - - // Add ext_proc filter for policy engine - extProcFilter, err := t.createExtProcFilter() - if err != nil { - return nil, fmt.Errorf("failed to create ext_proc filter: %w", err) - } - httpFilters = append(httpFilters, extProcFilter) - - luaFilter, err := t.createLuaFilter() - if err != nil { - return nil, fmt.Errorf("failed to create lua filter: %w", err) - } - httpFilters = append(httpFilters, luaFilter) - - dnsCacheConfig := &common_dfp.DnsCacheConfig{ - // Required: unique name for the shared DNS cache - Name: "dynamic_forward_proxy_cache", - - // Optional: how often DNS entries are refreshed - DnsRefreshRate: durationpb.New(60 * time.Second), - - // Optional: how long hosts stay cached - HostTtl: durationpb.New(300 * time.Second), - - // Optional: which DNS families to use (AUTO, V4_ONLY, V6_ONLY) - DnsLookupFamily: cluster.Cluster_V4_PREFERRED, - - MaxHosts: &wrapperspb.UInt32Value{Value: 1024}, - } - - dfpFilterConfig := &dfpv3.FilterConfig{ - ImplementationSpecifier: &dfpv3.FilterConfig_DnsCacheConfig{ - DnsCacheConfig: dnsCacheConfig, - }, - } - // Dynamic forward proxy filter config placeholder (typed config fields omitted for compatibility with current go-control-plane version) - dynamicFwdAny, err := anypb.New(dfpFilterConfig) - - if err != nil { - return nil, fmt.Errorf("failed to marshal dynamic forward proxy config: %w", err) - } - - // Router filter - routerConfig := &router.Router{} - routerAny, err := anypb.New(routerConfig) - if err != nil { - return nil, fmt.Errorf("failed to marshal router config: %w", err) - } - - // Add dynamic forward proxy router filter (must be last) - httpFilters = append(httpFilters, &hcm.HttpFilter{ - Name: wellknown.Router, - ConfigType: &hcm.HttpFilter_TypedConfig{ - TypedConfig: dynamicFwdAny, - }, - }) - - // Add router filter (must be last) - httpFilters = append(httpFilters, &hcm.HttpFilter{ - Name: wellknown.Router, - ConfigType: &hcm.HttpFilter_TypedConfig{ - TypedConfig: routerAny, - }, - }) - - // Create HTTP connection manager - httpConnManager := &hcm.HttpConnectionManager{ - CodecType: hcm.HttpConnectionManager_AUTO, - StatPrefix: "http", - GenerateRequestId: wrapperspb.Bool(true), - RouteSpecifier: &hcm.HttpConnectionManager_RouteConfig{ - RouteConfig: dynamicForwardProxyRouteConfig, - }, - HttpFilters: httpFilters, - } - - // httpConnManager := &hcm.HttpConnectionManager{ - // StatPrefix: "WEBSUBHUB_INBOUND_8082_LISTENER", - // CodecType: hcm.HttpConnectionManager_AUTO, - // RouteSpecifier: &hcm.HttpConnectionManager_RouteConfig{RouteConfig: dynamicForwardProxyRouteConfig}, - // HttpFilters: []*hcm.HttpFilter{ - // { // dynamic forward proxy filter - // Name: "envoy.filters.http.dynamic_forward_proxy", - // ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: dynamicFwdAny}, - // }, - // { // router filter must be last - // Name: wellknown.Router, - // ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: routerAny}, - // }, - // }, - // } - - // Attach access logs if enabled - if t.routerConfig.AccessLogs.Enabled { - accessLogs, err := t.createAccessLogConfig() - if err != nil { - return nil, fmt.Errorf("failed to create access log config: %w", err) - } - httpConnManager.AccessLog = accessLogs - } - - // Add tracing if enabled - tracingCfgDFP, err := t.createTracingConfig() - if err != nil { - return nil, fmt.Errorf("failed to create tracing config: %w", err) - } - if tracingCfgDFP != nil { - httpConnManager.Tracing = tracingCfgDFP - } - - // hcmAny, err := anypb.New(httpConnManager) - // if err != nil { - // return nil, fmt.Errorf("failed to marshal http connection manager: %w", err) - // } - - pbst, err := anypb.New(httpConnManager) - if err != nil { - return nil, err - } - - // Determine listener name and port based on protocol - // TODO: Use config values for port - var listenerName string - var port uint32 - if isHTTPS { - listenerName = fmt.Sprintf("listener_https_%d", constants.WEBSUB_HUB_DYNAMIC_HTTPS_PORT) - port = uint32(constants.WEBSUB_HUB_DYNAMIC_HTTPS_PORT) - } else { - listenerName = fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_DYNAMIC_HTTP_PORT) - port = uint32(constants.WEBSUB_HUB_DYNAMIC_HTTP_PORT) - } - - //TODO: Add TLS Filter chain for HTTPS - // if isHTTPS { - // tlsContext, err := t.createDownstreamTLSContext() - // if err != nil { - // return nil, fmt.Errorf("failed to create downstream TLS context: %w", err) - // } - - // tlsContextAny, err := anypb.New(tlsContext) - // if err != nil { - // return nil, fmt.Errorf("failed to marshal downstream TLS context: %w", err) - // } - - // filterChain.TransportSocket = &core.TransportSocket{ - // Name: "envoy.transport_sockets.tls", - // ConfigType: &core.TransportSocket_TypedConfig{ - // TypedConfig: tlsContextAny, - // }, - // } - // } - - // Create filter chain - filterChain := &listener.FilterChain{ - Filters: []*listener.Filter{{ - Name: wellknown.HTTPConnectionManager, - ConfigType: &listener.Filter_TypedConfig{ - TypedConfig: pbst, - }, - }}, - } - - return &listener.Listener{ - Name: listenerName, - Address: &core.Address{Address: &core.Address_SocketAddress{SocketAddress: &core.SocketAddress{ - Protocol: core.SocketAddress_TCP, - Address: "0.0.0.0", - PortSpecifier: &core.SocketAddress_PortValue{PortValue: port}, - }}}, - FilterChains: []*listener.FilterChain{filterChain}, - // FilterChains: []*listener.FilterChain{{ - // Filters: []*listener.Filter{{ - // Name: wellknown.HTTPConnectionManager, - // ConfigType: &listener.Filter_TypedConfig{TypedConfig: hcmAny}, - // }}, - // }}, - }, nil -} - // createRouteConfiguration creates a route configuration // Uses SharedRouteConfigName so it can be discovered via RDS func (t *Translator) createRouteConfiguration(virtualHosts []*route.VirtualHost) *route.RouteConfiguration { @@ -2983,37 +2467,6 @@ func (t *Translator) processEndpoint( return []*endpoint.LocalityLbEndpoints{localityLbEndpoints}, nil } -// createDynamicForwardProxyCluster creates a dynamic forward proxy cluster for WebSubHub -func (t *Translator) createDynamicForwardProxyCluster() *cluster.Cluster { - // Note: Due to go-control-plane API limitations, we use a placeholder Any for the typed config - // The actual DNS cache config should match the filter config in createListenerForWebSubHub - clusterConfig := &dfpcluster.ClusterConfig{ - // optional: control connection pooling / subclusters here - } - clusterTypeAny, err := anypb.New(clusterConfig) - if err != nil { - t.logger.Error("Failed to marshal dynamic forward proxy cluster config", slog.Any("error", err)) - return nil - } - - return &cluster.Cluster{ - Name: DynamicForwardProxyClusterName, - ConnectTimeout: durationpb.New(5 * time.Second), - LbPolicy: cluster.Cluster_CLUSTER_PROVIDED, - ClusterDiscoveryType: &cluster.Cluster_ClusterType{ - ClusterType: &cluster.Cluster_CustomClusterType{ - Name: "envoy.clusters.dynamic_forward_proxy", - TypedConfig: clusterTypeAny, - }, - }, - UpstreamConnectionOptions: &cluster.UpstreamConnectionOptions{ - TcpKeepalive: &core.TcpKeepalive{ - KeepaliveTime: &wrapperspb.UInt32Value{Value: 300}, - }, - }, - } -} - // pathToRegex converts a path with parameters to a regex pattern // Converts paths like /weather/v1.0/{country_code}/{city} to ^/weather/v1\.0/[^/]+/[^/]+$ // Special characters (like dots in version) are escaped, but {params} become [^/]+ patterns diff --git a/gateway/gateway-controller/pkg/xds/translator_test.go b/gateway/gateway-controller/pkg/xds/translator_test.go index 81751a2785..8b8c1f2ecb 100644 --- a/gateway/gateway-controller/pkg/xds/translator_test.go +++ b/gateway/gateway-controller/pkg/xds/translator_test.go @@ -19,7 +19,6 @@ package xds import ( - "fmt" "math" "net/url" "regexp" @@ -30,7 +29,6 @@ import ( accesslog "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" @@ -1971,17 +1969,6 @@ func TestNotEffectivelyMatchesPrefix(t *testing.T) { } } -func TestTranslator_CreateDynamicForwardProxyCluster(t *testing.T) { - logger := createTestLogger() - routerCfg := testRouterConfig() - cfg := testConfig() - translator := NewTranslator(logger, routerCfg, nil, cfg) - - cluster := translator.createDynamicForwardProxyCluster() - assert.NotNil(t, cluster) - assert.Equal(t, DynamicForwardProxyClusterName, cluster.Name) -} - func TestTranslator_CreateSDSCluster(t *testing.T) { logger := createTestLogger() routerCfg := testRouterConfig() @@ -2352,340 +2339,6 @@ func createTestTranslator() *Translator { return NewTranslator(logger, routerCfg, nil, cfg) } -// Tests for lines 310-351: Event gateway WebSub hub configuration -func TestTranslator_TranslateConfigs_WebSubHub_Enabled(t *testing.T) { - t.Run("Event gateway enabled creates WebSub listeners and clusters", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com:8080" - translator.routerConfig.EventGateway.WebSubHubPort = 8080 - translator.routerConfig.HTTPSEnabled = false - - // Empty config list to just test WebSub infrastructure - resources, err := translator.TranslateConfigs([]*models.StoredConfig{}, "test") - require.NoError(t, err) - assert.NotNil(t, resources) - - // Verify that WebSub clusters and listeners were created - clusters := resources[resource.ClusterType] - listeners := resources[resource.ListenerType] - - // Should contain WebSub internal cluster and dynamic forward proxy cluster - clusterNames := make([]string, 0) - for _, c := range clusters { - clusterNames = append(clusterNames, c.(*cluster.Cluster).GetName()) - } - assert.Contains(t, clusterNames, constants.WEBSUBHUB_INTERNAL_CLUSTER_NAME) - assert.Contains(t, clusterNames, DynamicForwardProxyClusterName) - - // Should contain listeners for WebSub - listenerNames := make([]string, 0) - for _, l := range listeners { - listenerNames = append(listenerNames, l.(*listener.Listener).GetName()) - } - // Check for internal listener - assert.Contains(t, listenerNames, fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_INTERNAL_HTTP_PORT)) - }) - - t.Run("Event gateway with HTTPS enabled creates HTTPS listener", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "https://websub.example.com" - translator.routerConfig.EventGateway.WebSubHubPort = 8443 - translator.routerConfig.HTTPSEnabled = false // Set to false to avoid TLS cert errors - - resources, err := translator.TranslateConfigs([]*models.StoredConfig{}, "test") - require.NoError(t, err) - - listeners := resources[resource.ListenerType] - listenerNames := make([]string, 0) - for _, l := range listeners { - listenerNames = append(listenerNames, l.(*listener.Listener).GetName()) - } - - // Should have HTTP listener for WebSub (HTTPS is disabled) - assert.Contains(t, listenerNames, fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_INTERNAL_HTTP_PORT)) - }) - - t.Run("Event gateway URL parsing with missing port", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com" - translator.routerConfig.EventGateway.WebSubHubPort = 9090 - - resources, err := translator.TranslateConfigs([]*models.StoredConfig{}, "test") - require.NoError(t, err) - assert.NotNil(t, resources) - }) - - t.Run("Event gateway URL parsing with missing scheme", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "websub.example.com:8080" - translator.routerConfig.EventGateway.WebSubHubPort = 8080 - - resources, err := translator.TranslateConfigs([]*models.StoredConfig{}, "test") - require.NoError(t, err) - assert.NotNil(t, resources) - }) - - t.Run("Event gateway with invalid URL", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "://invalid-url" - - _, err := translator.TranslateConfigs([]*models.StoredConfig{}, "test") - assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid upstream URL") - }) -} - -// Tests for lines 400-447: translateAsyncAPIConfig method -func TestTranslator_TranslateAsyncAPIConfig(t *testing.T) { - t.Run("Translate valid WebSub API config", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com:8080" - - webhookConfig := &models.StoredConfig{ - UUID: "0000-websub-api-1-0000-000000000000", - Kind: "WebSubApi", - Configuration: api.WebSubAPI{ - Metadata: api.Metadata{ - Name: "websub-test", - Annotations: &map[string]string{"gateway.api-platform.wso2.com/project-id": "proj-123"}, - }, - Kind: api.WebSubAPIKindWebSubApi, - ApiVersion: api.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1, - Spec: api.WebhookAPIData{ - DisplayName: "WebSub Test API", - Version: "v1.0", - Context: "/webhook", - Channels: &map[string]api.WebSubChannel{ - "/topic1": {}, - "topic2": {}, - }, - }, - }, - Origin: models.OriginGatewayAPI, - } - - routes, clusters, err := translator.translateAsyncAPIConfig(webhookConfig, []*models.StoredConfig{}) - require.NoError(t, err) - assert.NotNil(t, routes) - assert.NotNil(t, clusters) - - // Should create route for each channel plus the main route - assert.GreaterOrEqual(t, len(routes), 2) - - // Verify routes are created correctly - for _, r := range routes { - assert.NotNil(t, r.GetMatch()) - assert.Equal(t, constants.WEBSUBHUB_INTERNAL_CLUSTER_NAME, r.GetRoute().GetCluster()) - } - }) - - t.Run("WebSub API with invalid URL", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "://invalid" - - webhookConfig := &models.StoredConfig{ - UUID: "0000-websub-api-2-0000-000000000000", - Kind: "WebSubApi", - Configuration: api.WebSubAPI{ - Metadata: api.Metadata{Name: "websub-invalid"}, - Kind: api.WebSubAPIKindWebSubApi, - ApiVersion: api.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1, - Spec: api.WebhookAPIData{ - DisplayName: "WebSub Invalid", - Version: "v1.0", - Context: "/webhook", - Channels: &map[string]api.WebSubChannel{ - "/test": {}, - }, - }, - }, - Origin: models.OriginGatewayAPI, - } - - _, _, err := translator.translateAsyncAPIConfig(webhookConfig, []*models.StoredConfig{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid upstream URL") - }) -} - -// Tests for lines 697-834: createInternalListenerForWebSubHub method -func TestTranslator_CreateInternalListenerForWebSubHub(t *testing.T) { - t.Run("Create HTTP internal listener", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.AccessLogs.Enabled = false - - listener, err := translator.createInternalListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - - // Verify listener name and port - expectedName := fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_INTERNAL_HTTP_PORT) - assert.Equal(t, expectedName, listener.GetName()) - assert.Equal(t, uint32(constants.WEBSUB_HUB_INTERNAL_HTTP_PORT), listener.GetAddress().GetSocketAddress().GetPortValue()) - - // Verify filter chain exists - assert.NotEmpty(t, listener.GetFilterChains()) - filterChain := listener.GetFilterChains()[0] - assert.NotNil(t, filterChain) - - // Should not have TLS for HTTP - assert.Nil(t, filterChain.GetTransportSocket()) - }) - - t.Run("Create HTTPS internal listener with TLS", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.AccessLogs.Enabled = false - - // This test will fail without proper TLS certs, so we expect an error - _, err := translator.createInternalListenerForWebSubHub(true) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to create downstream TLS context") - }) - - t.Run("Create listener with policy engine enabled", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.PolicyEngine.Host = "policy-engine" - translator.routerConfig.PolicyEngine.Port = 9002 - translator.routerConfig.LuaScriptPath = "../../lua/request_transformation.lua" - - listener, err := translator.createInternalListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - - // Verify listener was created successfully with ext_proc filter - assert.NotEmpty(t, listener.GetFilterChains()) - }) - - t.Run("Create listener with access logs enabled", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.AccessLogs.Enabled = true - translator.routerConfig.AccessLogs.Format = "json" - translator.routerConfig.AccessLogs.JSONFields = map[string]string{ - "start_time": "%START_TIME%", - "method": "%REQ(:METHOD)%", - } - - listener, err := translator.createInternalListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - }) - - t.Run("Create listener with tracing enabled", func(t *testing.T) { - translator := createTestTranslator() - translator.config.TracingConfig.Enabled = true - translator.config.TracingConfig.Endpoint = "otel-collector:4317" - - listener, err := translator.createInternalListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - }) -} - -// Tests for lines 913-1108: createDynamicFwdListenerForWebSubHub method -func TestTranslator_CreateDynamicFwdListenerForWebSubHub(t *testing.T) { - t.Run("Create HTTP dynamic forward proxy listener", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com:8080" - translator.routerConfig.AccessLogs.Enabled = false - - listener, err := translator.createDynamicFwdListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - - // Verify listener name and port - expectedName := fmt.Sprintf("listener_http_%d", constants.WEBSUB_HUB_DYNAMIC_HTTP_PORT) - assert.Equal(t, expectedName, listener.GetName()) - assert.Equal(t, uint32(constants.WEBSUB_HUB_DYNAMIC_HTTP_PORT), listener.GetAddress().GetSocketAddress().GetPortValue()) - - // Verify filter chain - assert.NotEmpty(t, listener.GetFilterChains()) - filterChain := listener.GetFilterChains()[0] - assert.NotNil(t, filterChain) - assert.NotEmpty(t, filterChain.GetFilters()) - }) - - t.Run("Create HTTPS dynamic forward proxy listener", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "https://websub.example.com" - translator.routerConfig.AccessLogs.Enabled = false - - listener, err := translator.createDynamicFwdListenerForWebSubHub(true) - require.NoError(t, err) - assert.NotNil(t, listener) - - // Verify listener name and port - expectedName := fmt.Sprintf("listener_https_%d", constants.WEBSUB_HUB_DYNAMIC_HTTPS_PORT) - assert.Equal(t, expectedName, listener.GetName()) - assert.Equal(t, uint32(constants.WEBSUB_HUB_DYNAMIC_HTTPS_PORT), listener.GetAddress().GetSocketAddress().GetPortValue()) - }) - - t.Run("Create dynamic listener with policy engine enabled", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com:8080" - translator.routerConfig.PolicyEngine.Host = "policy-engine" - translator.routerConfig.PolicyEngine.Port = 9002 - translator.routerConfig.LuaScriptPath = "../../lua/request_transformation.lua" - - listener, err := translator.createDynamicFwdListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - - // Verify HTTP filters include ext_proc when policy engine is enabled - assert.NotEmpty(t, listener.GetFilterChains()) - }) - - t.Run("Create dynamic listener with access logs enabled", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com:8080" - translator.routerConfig.AccessLogs.Enabled = true - translator.routerConfig.AccessLogs.Format = "json" - translator.routerConfig.AccessLogs.JSONFields = map[string]string{ - "start_time": "%START_TIME%", - } - - listener, err := translator.createDynamicFwdListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - }) - - t.Run("Create dynamic listener with tracing enabled", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com:8080" - translator.config.TracingConfig.Enabled = true - translator.config.TracingConfig.Endpoint = "otel-collector:4317" - - listener, err := translator.createDynamicFwdListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - }) - - t.Run("Verify dynamic forward proxy configuration", func(t *testing.T) { - translator := createTestTranslator() - translator.routerConfig.EventGateway.Enabled = true - translator.routerConfig.EventGateway.WebSubHubURL = "http://websub.example.com:8080" - - listener, err := translator.createDynamicFwdListenerForWebSubHub(false) - require.NoError(t, err) - assert.NotNil(t, listener) - - // Verify the listener has the correct configuration - assert.Equal(t, "0.0.0.0", listener.GetAddress().GetSocketAddress().GetAddress()) - assert.Equal(t, core.SocketAddress_TCP, listener.GetAddress().GetSocketAddress().GetProtocol()) - }) -} - // TestCreateWeightedCluster_TLS guards the fix for the reviewer concern // "Configure TLS for weighted HTTPS upstreams." A multi-endpoint (weighted) upstream // definition whose endpoints are HTTPS must be dialed over TLS, mirroring the single-endpoint diff --git a/gateway/gateway-controller/tests/integration/storage_test.go b/gateway/gateway-controller/tests/integration/storage_test.go index 470f2b8f1c..7abd809f29 100644 --- a/gateway/gateway-controller/tests/integration/storage_test.go +++ b/gateway/gateway-controller/tests/integration/storage_test.go @@ -691,42 +691,6 @@ func TestConfigStore_LabelsWithAllAPITypes(t *testing.T) { assert.Equal(t, labels, retrieved) }) - t.Run("WebSubApi with labels", func(t *testing.T) { - asyncApiConfig := api.WebSubAPI{ - ApiVersion: api.WebSubAPIApiVersionGatewayApiPlatformWso2Comv1, - Kind: api.WebSubAPIKindWebSubApi, - Metadata: api.Metadata{ - Name: "async-api-v1.0", - Labels: &labels, - }, - Spec: api.WebhookAPIData{ - DisplayName: "AsyncAPILabel", - Version: "v1.0", - Context: "/async", - Channels: &map[string]api.WebSubChannel{ - "/events": {}, - }, - }, - } - cfg := &models.StoredConfig{ - UUID: uuid.New().String(), - Kind: string(api.WebSubAPIKindWebSubApi), - Handle: "async-api-v1.0", - DisplayName: "AsyncAPILabel", - Version: "v1.0", - Configuration: asyncApiConfig, - SourceConfiguration: asyncApiConfig, - DesiredState: models.StateDeployed, - Origin: models.OriginGatewayAPI, - } - - err := configStore.Add(cfg) - require.NoError(t, err) - - retrieved, err := configStore.GetLabelsMap(cfg.Handle) - assert.NoError(t, err) - assert.Equal(t, labels, retrieved) - }) } func TestSQLiteStorage_LabelsPersistence(t *testing.T) { diff --git a/gateway/it/go.mod b/gateway/it/go.mod index cf4af51808..2ad5b32fc5 100644 --- a/gateway/it/go.mod +++ b/gateway/it/go.mod @@ -19,6 +19,7 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect @@ -135,7 +136,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/oapi-codegen/runtime v1.1.2 // indirect + github.com/oapi-codegen/runtime v1.5.0 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -194,14 +195,14 @@ require ( go.opentelemetry.io/proto/otlp v1.9.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/crypto v0.50.0 // indirect + golang.org/x/net v0.52.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 + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/time v0.14.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/grpc v1.79.3 // indirect diff --git a/gateway/it/go.sum b/gateway/it/go.sum index da79dfa906..dd805b0198 100644 --- a/gateway/it/go.sum +++ b/gateway/it/go.sum @@ -13,6 +13,7 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6DKrR4/ljxn6SF6nURyu785wKMuQcjt7H3VCQ= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -21,6 +22,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -58,6 +61,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= @@ -297,6 +301,7 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u 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/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -385,6 +390,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oapi-codegen/runtime v1.5.0 h1:aiil4QnH+eiWYSO60eaYZ4aur7sJH3rz6BvT5EBFnxc= +github.com/oapi-codegen/runtime v1.5.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= @@ -496,6 +503,7 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= 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= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -605,6 +613,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= 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= @@ -618,6 +627,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL 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/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -648,15 +658,20 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= 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.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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= diff --git a/go.work b/go.work index 1a6cbc0849..93e2238601 100644 --- a/go.work +++ b/go.work @@ -1,20 +1,21 @@ go 1.26.5 use ( - ./httpkit ./cli/it ./cli/src ./common ./event-gateway/gateway-builder - ./event-gateway/webhook-listener + ./event-gateway/gateway-controller ./event-gateway/gateway-runtime ./event-gateway/it + ./event-gateway/webhook-listener ./gateway/gateway-builder ./gateway/gateway-controller ./gateway/gateway-runtime/policy-engine ./gateway/it ./gateway/sample-policies/transform-payload-case ./gateway/system-policies/analytics + ./httpkit ./kubernetes/conformance/runner ./kubernetes/gateway-operator ./platform-api diff --git a/go.work.sum b/go.work.sum index 388c90c173..24185010b6 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2095,6 +2095,7 @@ github.com/aliyun/credentials-go v1.2.7/go.mod h1:/KowD1cfGSLrLsH28Jr8W+xwoId0yw github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0/go.mod h1:ccdDYaY5+gO+cbnQdFxEXqfy0RkoV25H3jLXUDNM3wg= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= @@ -2205,12 +2206,12 @@ github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= @@ -2668,6 +2669,7 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38 h1:y0Wmhvml7cGnzPa9nocn/fMraMH/lMDdeG+rkx4VgYY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -2726,7 +2728,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-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= 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= @@ -2911,10 +2912,6 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -2994,10 +2991,13 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= +github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk= github.com/kataras/golog v0.1.9/go.mod h1:jlpk/bOaYCyqDqH18pgDHdaJab72yBE6i0O3s30hpWY= +github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 h1:Vx8kDVhO2qepK8w44lBtp+RzN3ld743i+LYPzODJSpQ= github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9/go.mod h1:ldkoR3iXABBeqlTibQ3MYaviA1oSlPvim6f55biwBh4= +github.com/kataras/iris/v12 v12.2.11/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w= github.com/kataras/jwt v0.1.10 h1:GBXOF9RVInDPhCFBiDumRG9Tt27l7ugLeLo8HL5SeKQ= github.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo= github.com/kataras/neffos v0.0.14 h1:pdJaTvUG3NQfeMbbVCI8JT2T5goPldyyfUB2PJfh1Bs= @@ -3005,6 +3005,7 @@ github.com/kataras/neffos v0.0.22 h1:3M4lHrUl//2OKmS9t9z3AKIZqwha6ABeA6WoF03HEv8 github.com/kataras/neffos v0.0.22/go.mod h1:IIJZcUDvwBxJGlDj942dqQgyznVKYDti91f8Ez+RRxE= github.com/kataras/pio v0.0.12 h1:o52SfVYauS3J5X08fNjlGS5arXHjW/ItLkyLcKjoH6w= github.com/kataras/pio v0.0.12/go.mod h1:ODK/8XBhhQ5WqrAhKy+9lTPS7sBf6O3KcLhc9klfRcY= +github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= @@ -3026,6 +3027,7 @@ github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= @@ -3058,6 +3060,7 @@ github.com/kris-nova/lolgopher v0.0.0-20180921204813-313b3abb0d9b h1:xYEM2oBUhBE github.com/kris-nova/lolgopher v0.0.0-20180921204813-313b3abb0d9b/go.mod h1:V0HF/ZBlN86HqewcDC/cVxMmYDiRukWjSrgKLUAn9Js= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= @@ -3092,6 +3095,8 @@ github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8 github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -3125,6 +3130,7 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/microsoft/go-mssqldb v1.9.4/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -3378,6 +3384,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= +github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -3423,6 +3431,7 @@ github.com/streamnative/pulsarctl v0.5.0/go.mod h1:K14cqq4IHMzPBK1mCKXxhF1mhgAnc github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/substrait-io/substrait-go v0.4.2 h1:buDnjsb3qAqTaNbOR7VKmNgXf4lYQxWEcnSGUWBtmN8= github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= @@ -3434,10 +3443,13 @@ github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/ github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RUB4zA= github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU= +github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM= github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA= github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= +github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0= github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.865 h1:LcUqBlKC4j15LhT303yQDX/XxyHG4haEQqbHgZZA4SY= @@ -3468,6 +3480,7 @@ github.com/tonistiigi/go-archvariant v1.0.0 h1:5LC1eDWiBNflnTF1prCiX09yfNHIxDC/a github.com/tonistiigi/go-archvariant v1.0.0/go.mod h1:TxFmO5VS6vMq2kvs3ht04iPXtu2rUT/erOnGFYfk5Ho= github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117 h1:XFwyh2JZwR5aiKLXHX2C1n0v5F11dCJpyGL1W/Cpl3U= github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117/go.mod h1:3Ez1Paeg+0Ghu3KwpEGC1HgZ4CHDlg+Ez/5Baeomk54= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twmb/franz-go v1.21.2 h1:WrvV/spF48JzcRylqDQy02Vm6V6W4lhtD9Y4BOYNMu4= github.com/twmb/franz-go v1.21.2/go.mod h1:rfoMTnVk7107fhTGxfEKIHP/e7tPe6oyij/ywzO0czk= github.com/twmb/franz-go/pkg/kmsg v1.12.0/go.mod h1:+DPt4NC8RmI6hqb8G09+3giKObE6uD2Eya6CfqBpeJY= @@ -3496,6 +3509,7 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/wso2/api-platform/sdk v0.3.14/go.mod h1:bH7GqWEZ+JzECJKPIva7JieYRH5AyuW6w9VI5LiAPBw= @@ -3639,6 +3653,7 @@ go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7 go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= 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= @@ -3677,6 +3692,7 @@ go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= 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= @@ -3703,6 +3719,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.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= 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= @@ -3804,7 +3821,7 @@ golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sU golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -3830,6 +3847,7 @@ golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= @@ -3909,11 +3927,11 @@ golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -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.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= 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= @@ -4003,7 +4021,6 @@ 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/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= 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= @@ -4078,6 +4095,7 @@ golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -4185,11 +4203,11 @@ 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.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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= @@ -4206,6 +4224,7 @@ golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c h1:6a8FdnNk6bTXBjR4AGK golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw= golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -4244,7 +4263,9 @@ golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -4277,7 +4298,7 @@ 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/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= 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= @@ -4380,12 +4401,11 @@ golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= diff --git a/platform-api/go.mod b/platform-api/go.mod index e74b78850e..5de4829f0c 100644 --- a/platform-api/go.mod +++ b/platform-api/go.mod @@ -15,7 +15,7 @@ require ( github.com/knadh/koanf/v2 v2.3.2 github.com/mattn/go-sqlite3 v1.14.41 github.com/microsoft/go-mssqldb v1.10.0 - github.com/oapi-codegen/runtime v1.1.2 + github.com/oapi-codegen/runtime v1.5.0 github.com/pb33f/libopenapi v0.28.2 github.com/stretchr/testify v1.11.1 github.com/wso2/api-platform/common v0.0.0 @@ -49,7 +49,7 @@ require ( golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.44.0 // indirect golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.12.0 // indirect + golang.org/x/time v0.14.0 // indirect ) replace github.com/wso2/api-platform/common => ../common diff --git a/platform-api/go.sum b/platform-api/go.sum index eeb23d2cc1..ef1d377a90 100644 --- a/platform-api/go.sum +++ b/platform-api/go.sum @@ -95,6 +95,8 @@ github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQ github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4= github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0= +github.com/oapi-codegen/runtime v1.5.0 h1:aiil4QnH+eiWYSO60eaYZ4aur7sJH3rz6BvT5EBFnxc= +github.com/oapi-codegen/runtime v1.5.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/pb33f/jsonpath v0.1.2 h1:PlqXjEyecMqoYJupLxYeClCGWEpAFnh4pmzgspbXDPI= github.com/pb33f/jsonpath v0.1.2/go.mod h1:TtKnUnfqZm48q7a56DxB3WtL3ipkVtukMKGKxaR/uXU= github.com/pb33f/libopenapi v0.28.2 h1:AXVCE8DWzytXu0jv0Z+cXVopnO/bXU1oWvgA9qiRWgw= @@ -130,6 +132,8 @@ golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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=