Auto generate OpenAPI spec (#2172)

* Generate openapi spec

Signed-off-by: ppadti <ppadti@redhat.com>

* Add swagger ui and fix format and reoragnize file

Signed-off-by: ppadti <ppadti@redhat.com>

* Fix ci failure

Signed-off-by: ppadti <ppadti@redhat.com>

* Fix validate openapi spec ci failure

Signed-off-by: ppadti <ppadti@redhat.com>

* Add a note in readme about two openapi spec

Signed-off-by: ppadti <ppadti@redhat.com>

* Fix swagger ui in dev mode and package naming

Signed-off-by: ppadti <ppadti@redhat.com>

---------

Signed-off-by: ppadti <ppadti@redhat.com>
This commit is contained in:
Pushpa Padti
2026-06-29 17:41:06 +00:00
committed by GitHub
parent 4b72817308
commit 2bb2ddf062
18 changed files with 502 additions and 15 deletions
+1
View File
@@ -229,6 +229,7 @@ clients/python/src/mr_openapi/models/serving_environment_list.py linguist-genera
clients/python/src/mr_openapi/models/serving_environment_update.py linguist-generated=true
clients/python/src/mr_openapi/models/sort_order.py linguist-generated=true
clients/python/src/mr_openapi/rest.py linguist-generated=true
clients/ui/bff/openapi/docs.go linguist-generated=true
internal/converter/generated/embedmd_openapi_converter.gen.go linguist-generated=true
internal/converter/generated/openapi_converter.gen.go linguist-generated=true
internal/converter/generated/openapi_embedmd_converter.gen.go linguist-generated=true
+4
View File
@@ -47,6 +47,10 @@ jobs:
working-directory: clients/ui/bff
run: make build
- name: Generate OpenAPI spec
working-directory: clients/ui/bff
run: make openapi
- name: Check if there are uncommitted file changes
working-directory: clients/ui/bff
run: |
+1
View File
@@ -45,6 +45,7 @@ RUN go mod download
# Copy the go source files
COPY ${BFF_SOURCE_CODE}/cmd/ cmd/
COPY ${BFF_SOURCE_CODE}/internal/ internal/
COPY ${BFF_SOURCE_CODE}/openapi/ openapi/
# Build the Go application
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o /usr/src/app/bff ./cmd
+1
View File
@@ -45,6 +45,7 @@ RUN go mod download
# Copy the go source files
COPY ${BFF_SOURCE_CODE}/cmd/ cmd/
COPY ${BFF_SOURCE_CODE}/internal/ internal/
COPY ${BFF_SOURCE_CODE}/openapi/ openapi/
# Build the Go application
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o /usr/src/app/bff ./cmd
+19 -2
View File
@@ -52,7 +52,7 @@ test: fmt vet envtest ## Runs the full test suite.
go test ./...
.PHONY: build
build: fmt vet test ## Builds the project to produce a binary executable.
build: fmt vet openapi test ## Builds the project to produce a binary executable. OpenAPI spec is generated before build.
ifeq ($(DEBUG), true) ## If DEBUG is true, build with debugging symbols
go build $(GCFLAGS_DEBUG) -o bin/bff ./cmd
else
@@ -60,7 +60,7 @@ else
endif
.PHONY: run
run: fmt vet envtest ## Runs the project.
run: fmt vet openapi envtest ## Runs the project. OpenAPI spec is generated before run.
trap 'exit 0' INT; \
ENVTEST_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" \
go run ./cmd --port=$(PORT) --auth-method=${AUTH_METHOD} --auth-token-header=$(AUTH_TOKEN_HEADER) --auth-token-prefix="$(AUTH_TOKEN_PREFIX)" --static-assets-dir=$(STATIC_ASSETS_DIR) --mock-k8s-client=$(MOCK_K8S_CLIENT) --mock-mr-client=$(MOCK_MR_CLIENT) --mock-mr-catalog-client=$(MOCK_MR_CATALOG_CLIENT) --dev-mode=$(DEV_MODE) --dev-mode-model-registry-port=$(DEV_MODE_MODEL_REGISTRY_PORT) --dev-mode-catalog-port=$(DEV_MODE_CATALOG_PORT) --deployment-mode=$(DEPLOYMENT_MODE) --log-level=$(LOG_LEVEL) --allowed-origins=$(ALLOWED_ORIGINS) --insecure-skip-verify=$(INSECURE_SKIP_VERIFY)
@@ -90,6 +90,23 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,${GOLANGCI_LINT_VERSION})
SWAG_VERSION ?= v1.16.4
SWAG ?= $(LOCALBIN)/swag-$(SWAG_VERSION)
.PHONY: swag
swag: $(SWAG) ## Download swag locally if necessary.
$(SWAG): $(LOCALBIN)
$(call go-install-tool,$(SWAG),github.com/swaggo/swag/cmd/swag,$(SWAG_VERSION))
ALL_GO_DIRS := $(shell find . -type f -name '*.go' -exec dirname {} \; | sed 's|^\./||' | sort -u)
ALL_GO_DIRS_NO_CMD := $(shell echo "$(ALL_GO_DIRS)" | tr ' ' '\n' | grep -v '^cmd$$' | paste -sd, -)
SWAG_DIRS := cmd,$(ALL_GO_DIRS_NO_CMD)
.PHONY: openapi
openapi: swag ## Generate OpenAPI spec from annotations
$(SWAG) fmt -g cmd/main.go -d $(SWAG_DIRS)
$(SWAG) init --parseDependency -q -g main.go -d $(SWAG_DIRS) -o openapi --outputTypes go,json,yaml --requiredByDefault
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary (ideally with version)
+2
View File
@@ -82,6 +82,8 @@ make docker-build
See the [OpenAPI specification](../api/openapi/mod-arch.yaml) for a complete list of endpoints.
> **Note:** We are in the process of moving from a manually maintained OpenAPI spec to a generated one. For a short period we have both: the hand-maintained [mod-arch.yaml](../api/openapi/mod-arch.yaml) and the generated [swagger.yaml](openapi/swagger.yaml) / [swagger.json](openapi/swagger.json) (produced from Go code via swaggo). The goal is to generate the OpenAPI spec from the codebase rather than managing it manually; once the transition is complete, the generated spec will be the single source of truth.
### Sample local calls
You will need to inject your requests with a `kubeflow-userid` header and namespace for authorization purposes.
+12
View File
@@ -1,3 +1,13 @@
// @title Model Registry BFF REST API
// @version 1.0.0
// @description REST API for Model Registry BFF
// @license.name Apache 2.0
// @license.url https://www.apache.org/licenses/LICENSE-2.0
// @host localhost:4000
// @BasePath /api/v1
// @schemes http https
package main
import (
@@ -15,6 +25,8 @@ import (
"net/http"
"os"
"time"
_ "github.com/kubeflow/hub/ui/bff/openapi" // swagger docs for Swagger UI
)
func main() {
+5
View File
@@ -11,6 +11,8 @@ require (
github.com/onsi/gomega v1.42.1
github.com/rs/cors v1.11.1
github.com/stretchr/testify v1.11.1
github.com/swaggo/http-swagger/v2 v2.0.2
github.com/swaggo/swag v1.16.4
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.36.2
k8s.io/apimachinery v0.36.2
@@ -19,6 +21,7 @@ require (
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -30,6 +33,7 @@ require (
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.20.6 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
@@ -48,6 +52,7 @@ require (
github.com/prometheus/procfs v0.19.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
+30
View File
@@ -1,3 +1,5 @@
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -6,6 +8,7 @@ github.com/brianvoe/gofakeit/v7 v7.15.0 h1:kGLYAWN8tnmxq2PelKVK6zwpM7kMxdz9SGPH3
github.com/brianvoe/gofakeit/v7 v7.15.0/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
@@ -28,10 +31,17 @@ 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/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
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-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
@@ -55,10 +65,16 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
@@ -98,8 +114,15 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/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/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -143,12 +166,19 @@ gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuB
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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=
k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY=
+9
View File
@@ -90,6 +90,10 @@ const (
McpServerFilterOptionListPath = McpServerCatalogPathPrefix + "/mcp_servers_filter_options"
McpServerPath = McpServerListPath + "/:" + McpServerId
McpServersToolListPath = McpServerPath + "/tools"
// Swagger UI (interactive API docs)
SwaggerPath = ApiPathPrefix + "/swagger"
SwaggerDocPath = SwaggerPath + "/doc.json"
)
type App struct {
@@ -279,6 +283,11 @@ func (app *App) Routes() http.Handler {
apiRouter.POST(CheckNamespaceRegistryAccessPath, app.CheckNamespaceRegistryAccessHandler)
apiRouter.GET(ModelRegistryListPath, app.AttachNamespace(app.RequireListServiceAccessInNamespace(app.GetAllModelRegistriesHandler)))
// Swagger UI (interactive API docs) — only in dev mode
if app.config.DevMode {
apiRouter.GET(SwaggerPath+"/*filepath", app.GetSwaggerHandler)
}
// Enable these routes in all cases except Kubeflow integration mode
// (Kubeflow integration mode is when DeploymentMode is kubeflow)
isKubeflowIntegrationMode := app.config.DeploymentMode.IsKubeflowMode()
@@ -9,7 +9,7 @@ import (
"github.com/kubeflow/hub/ui/bff/internal/config"
"github.com/kubeflow/hub/ui/bff/internal/mocks"
"github.com/kubeflow/hub/ui/bff/internal/models"
"github.com/kubeflow/hub/ui/bff/internal/models/healthcheck"
"github.com/kubeflow/hub/ui/bff/internal/repositories"
"github.com/stretchr/testify/assert"
)
@@ -35,15 +35,15 @@ func TestHealthCheckHandler(t *testing.T) {
body, err := io.ReadAll(rs.Body)
assert.NoError(t, err)
var healthCheckRes models.HealthCheckModel
var healthCheckRes healthcheck.HealthCheckModel
err = json.Unmarshal(body, &healthCheckRes)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rr.Code)
expected := models.HealthCheckModel{
expected := healthcheck.HealthCheckModel{
Status: "available",
SystemInfo: models.SystemInfo{
SystemInfo: healthcheck.SystemInfo{
Version: Version,
},
}
@@ -4,9 +4,26 @@ import (
"net/http"
"github.com/julienschmidt/httprouter"
// imported for swag documentation
_ "github.com/kubeflow/hub/ui/bff/internal/models/healthcheck"
)
func (app *App) HealthcheckHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// HealthcheckHandler returns the health status of the application.
//
// @Summary Returns the health status of the application
// @Description Provides a healthcheck response indicating the status of key services.
// @Tags healthcheck
// @ID getHealthcheck
// @Produce application/json
// @Success 200 {object} healthcheck.HealthCheckModel "Successful healthcheck response"
// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required."
// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to access the resource."
// @Failure 404 {object} ErrorEnvelope "Not Found. Resource does not exist."
// @Failure 422 {object} ErrorEnvelope "Unprocessable Entity. Validation error."
// @Failure 500 {object} ErrorEnvelope "Internal server error"
// @Router /healthcheck [get]
func (app *App) HealthcheckHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
healthCheck, err := app.repositories.HealthCheck.HealthCheck(Version)
if err != nil {
app.serverErrorResponse(w, r, err)
@@ -0,0 +1,34 @@
/*
Copyright 2026.
Licensed 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 api
import (
"net/http"
"github.com/julienschmidt/httprouter"
httpSwagger "github.com/swaggo/http-swagger/v2"
)
// GetSwaggerHandler serves the Swagger UI for interactive API documentation.
func (app *App) GetSwaggerHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
httpSwagger.Handler(
httpSwagger.URL(SwaggerDocPath),
httpSwagger.DeepLinking(true),
httpSwagger.DocExpansion("list"),
httpSwagger.DomID("swagger-ui"),
).ServeHTTP(w, r)
}
@@ -1,4 +1,4 @@
package models
package healthcheck
type SystemInfo struct {
Version string `json:"version"`
@@ -1,6 +1,6 @@
package repositories
import "github.com/kubeflow/hub/ui/bff/internal/models"
import "github.com/kubeflow/hub/ui/bff/internal/models/healthcheck"
type HealthCheckRepository struct{}
@@ -8,13 +8,11 @@ func NewHealthCheckRepository() *HealthCheckRepository {
return &HealthCheckRepository{}
}
func (r *HealthCheckRepository) HealthCheck(version string) (models.HealthCheckModel, error) {
var res = models.HealthCheckModel{
func (r *HealthCheckRepository) HealthCheck(version string) (healthcheck.HealthCheckModel, error) {
return healthcheck.HealthCheckModel{
Status: "available",
SystemInfo: models.SystemInfo{
SystemInfo: healthcheck.SystemInfo{
Version: version,
},
}
return res, nil
}, nil
}
+146
View File
@@ -0,0 +1,146 @@
// Package openapi Code generated by swaggo/swag. DO NOT EDIT
package openapi
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/healthcheck": {
"get": {
"description": "Provides a healthcheck response indicating the status of key services.",
"produces": [
"application/json"
],
"tags": [
"healthcheck"
],
"summary": "Returns the health status of the application",
"operationId": "getHealthcheck",
"responses": {
"200": {
"description": "Successful healthcheck response",
"schema": {
"$ref": "#/definitions/healthcheck.HealthCheckModel"
}
},
"401": {
"description": "Unauthorized. Authentication is required.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"403": {
"description": "Forbidden. User does not have permission to access the resource.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"404": {
"description": "Not Found. Resource does not exist.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"422": {
"description": "Unprocessable Entity. Validation error.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
}
}
}
}
},
"definitions": {
"api.ErrorEnvelope": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"$ref": "#/definitions/httpclient.HTTPError"
}
}
},
"healthcheck.HealthCheckModel": {
"type": "object",
"required": [
"status",
"system_info"
],
"properties": {
"status": {
"type": "string"
},
"system_info": {
"$ref": "#/definitions/healthcheck.SystemInfo"
}
}
},
"healthcheck.SystemInfo": {
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"type": "string"
}
}
},
"httpclient.HTTPError": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0.0",
Host: "localhost:4000",
BasePath: "/api/v1",
Schemes: []string{"http", "https"},
Title: "Model Registry BFF REST API",
Description: "REST API for Model Registry BFF",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
+126
View File
@@ -0,0 +1,126 @@
{
"schemes": [
"http",
"https"
],
"swagger": "2.0",
"info": {
"description": "REST API for Model Registry BFF",
"title": "Model Registry BFF REST API",
"contact": {},
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0"
},
"version": "1.0.0"
},
"host": "localhost:4000",
"basePath": "/api/v1",
"paths": {
"/healthcheck": {
"get": {
"description": "Provides a healthcheck response indicating the status of key services.",
"produces": [
"application/json"
],
"tags": [
"healthcheck"
],
"summary": "Returns the health status of the application",
"operationId": "getHealthcheck",
"responses": {
"200": {
"description": "Successful healthcheck response",
"schema": {
"$ref": "#/definitions/healthcheck.HealthCheckModel"
}
},
"401": {
"description": "Unauthorized. Authentication is required.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"403": {
"description": "Forbidden. User does not have permission to access the resource.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"404": {
"description": "Not Found. Resource does not exist.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"422": {
"description": "Unprocessable Entity. Validation error.",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/api.ErrorEnvelope"
}
}
}
}
}
},
"definitions": {
"api.ErrorEnvelope": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"$ref": "#/definitions/httpclient.HTTPError"
}
}
},
"healthcheck.HealthCheckModel": {
"type": "object",
"required": [
"status",
"system_info"
],
"properties": {
"status": {
"type": "string"
},
"system_info": {
"$ref": "#/definitions/healthcheck.SystemInfo"
}
}
},
"healthcheck.SystemInfo": {
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"type": "string"
}
}
},
"httpclient.HTTPError": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
+84
View File
@@ -0,0 +1,84 @@
basePath: /api/v1
definitions:
api.ErrorEnvelope:
properties:
error:
$ref: '#/definitions/httpclient.HTTPError'
required:
- error
type: object
healthcheck.HealthCheckModel:
properties:
status:
type: string
system_info:
$ref: '#/definitions/healthcheck.SystemInfo'
required:
- status
- system_info
type: object
healthcheck.SystemInfo:
properties:
version:
type: string
required:
- version
type: object
httpclient.HTTPError:
properties:
code:
type: string
message:
type: string
required:
- code
- message
type: object
host: localhost:4000
info:
contact: {}
description: REST API for Model Registry BFF
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
title: Model Registry BFF REST API
version: 1.0.0
paths:
/healthcheck:
get:
description: Provides a healthcheck response indicating the status of key services.
operationId: getHealthcheck
produces:
- application/json
responses:
"200":
description: Successful healthcheck response
schema:
$ref: '#/definitions/healthcheck.HealthCheckModel'
"401":
description: Unauthorized. Authentication is required.
schema:
$ref: '#/definitions/api.ErrorEnvelope'
"403":
description: Forbidden. User does not have permission to access the resource.
schema:
$ref: '#/definitions/api.ErrorEnvelope'
"404":
description: Not Found. Resource does not exist.
schema:
$ref: '#/definitions/api.ErrorEnvelope'
"422":
description: Unprocessable Entity. Validation error.
schema:
$ref: '#/definitions/api.ErrorEnvelope'
"500":
description: Internal server error
schema:
$ref: '#/definitions/api.ErrorEnvelope'
summary: Returns the health status of the application
tags:
- healthcheck
schemes:
- http
- https
swagger: "2.0"