From 0c6dd58fc57f3f7d3153bbb1265735e0cd00dee9 Mon Sep 17 00:00:00 2001 From: Sakala Venkata Krishna Rohit Date: Thu, 11 Sep 2025 11:52:32 -0700 Subject: [PATCH] Add functions ServeAPIUI apiUIPath for api ui assets (#806) --- go.mod | 2 +- go.sum | 4 +-- pkg/ui/handler.go | 85 ++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index da8e867a..fee96d2f 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/pborman/uuid v1.2.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.22.0 - github.com/rancher/apiserver v0.7.2 + github.com/rancher/apiserver v0.7.3 github.com/rancher/dynamiclistener v0.7.0 github.com/rancher/kubernetes-provider-detector v0.1.5 github.com/rancher/lasso v0.2.3 diff --git a/go.sum b/go.sum index a352ebfc..3cf28b89 100644 --- a/go.sum +++ b/go.sum @@ -214,8 +214,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rancher/apiserver v0.7.2 h1:mDgVHgDF0DJ2AmN6KGzdlk1ueAJJJGYOrJhXG+1LNn8= -github.com/rancher/apiserver v0.7.2/go.mod h1:9b/n58YT7S8bMFEyr1v7xzL72qwZKQQJK2Ir6lMT8Yk= +github.com/rancher/apiserver v0.7.3 h1:a9yibIRiZe2kQ5X+x5cXgajff8S9j3sTu7+kxT6g4hs= +github.com/rancher/apiserver v0.7.3/go.mod h1:9b/n58YT7S8bMFEyr1v7xzL72qwZKQQJK2Ir6lMT8Yk= github.com/rancher/dynamiclistener v0.7.0 h1:+jyfZ4lVamc1UbKWo8V8dhSPtCgRZYaY8nm7wiHeko4= github.com/rancher/dynamiclistener v0.7.0/go.mod h1:Q2YA42xp7Xc69JiSlJ8GpvLvze261T0iQ/TP4RdMCYk= github.com/rancher/kubernetes-provider-detector v0.1.5 h1:hWRAsWuJOemzGjz/XrbTlM7QmfO4OedvFE3QwXiH60I= diff --git a/pkg/ui/handler.go b/pkg/ui/handler.go index 38bf2d55..2af5c1d2 100644 --- a/pkg/ui/handler.go +++ b/pkg/ui/handler.go @@ -1,11 +1,13 @@ package ui import ( + "fmt" "io" "io/ioutil" "net/http" "os" "path/filepath" + "strings" "sync" "github.com/rancher/apiserver/pkg/middleware" @@ -20,12 +22,13 @@ type StringSetting func() string type BoolSetting func() bool type Handler struct { - pathSetting func() string - indexSetting func() string - releaseSetting func() bool - offlineSetting func() string - middleware func(http.Handler) http.Handler - indexMiddleware func(http.Handler) http.Handler + pathSetting func() string + indexSetting func() string + releaseSetting func() bool + offlineSetting func() string + apiUIVersionSetting func() string + middleware func(http.Handler) http.Handler + indexMiddleware func(http.Handler) http.Handler downloadOnce sync.Once downloadSuccess bool @@ -40,6 +43,8 @@ type Options struct { Offline StringSetting // Whether or not is it release, if true UI will run offline if set to dynamic ReleaseSetting BoolSetting + // The version of API UI to use + APIUIVersionSetting StringSetting } func NewUIHandler(opts *Options) *Handler { @@ -48,10 +53,11 @@ func NewUIHandler(opts *Options) *Handler { } h := &Handler{ - indexSetting: opts.Index, - offlineSetting: opts.Offline, - pathSetting: opts.Path, - releaseSetting: opts.ReleaseSetting, + indexSetting: opts.Index, + offlineSetting: opts.Offline, + pathSetting: opts.Path, + releaseSetting: opts.ReleaseSetting, + apiUIVersionSetting: opts.APIUIVersionSetting, middleware: middleware.Chain{ middleware.Gzip, middleware.FrameOptions, @@ -94,7 +100,7 @@ func NewUIHandler(opts *Options) *Handler { func (u *Handler) canDownload(url string) bool { u.downloadOnce.Do(func() { - if err := serveIndex(ioutil.Discard, url); err == nil { + if err := serveRemote(ioutil.Discard, url); err == nil { u.downloadSuccess = true } else { logrus.Errorf("Failed to download %s, falling back to packaged UI", url) @@ -103,7 +109,7 @@ func (u *Handler) canDownload(url string) bool { return u.downloadSuccess } -func (u *Handler) path() (path string, isURL bool) { +func (u *Handler) indexPath() (path string, isURL bool) { switch u.offlineSetting() { case "dynamic": if u.releaseSetting() { @@ -120,6 +126,46 @@ func (u *Handler) path() (path string, isURL bool) { } } +func (u *Handler) apiUIPath() (string, bool) { + version := u.apiUIVersionSetting() + remotePath := "https://releases.rancher.com/api-ui/" + + switch u.offlineSetting() { + case "dynamic": + if u.releaseSetting() { + return filepath.Join(u.pathSetting(), "api-ui"), false + } + if u.canDownload(fmt.Sprintf("%s/%s/%s", remotePath, version, "ui.min.js")) { + return remotePath, true + } + return filepath.Join(u.pathSetting(), "api-ui"), false + case "true": + return filepath.Join(u.pathSetting(), "api-ui"), false + default: + return remotePath, true + } +} + +func (u *Handler) ServeAPIUI() http.Handler { + return u.middleware(http.StripPrefix("/api-ui/", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + base, isURL := u.apiUIPath() + if isURL { + remoteURL := base + req.URL.Path + if err := serveRemote(rw, remoteURL); err != nil { + logrus.Errorf("failed to fetch asset from %s: %v", remoteURL, err) + } + } else { + parts := strings.SplitN(req.URL.Path, "/", 2) + if len(parts) == 2 { + http.ServeFile(rw, req, filepath.Join(base, parts[1])) + } else { + http.NotFound(rw, req) + } + } + }))) +} + func (u *Handler) ServeAsset() http.Handler { return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { http.FileServer(http.Dir(u.pathSetting())).ServeHTTP(rw, req) @@ -145,8 +191,8 @@ func (u *Handler) IndexFileOnNotFound() http.Handler { func (u *Handler) IndexFile() http.Handler { return u.indexMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if path, isURL := u.path(); isURL { - err := serveIndex(rw, path) + if path, isURL := u.indexPath(); isURL { + err := serveRemote(rw, path) if err != nil { logrus.Errorf("failed to download %s: %v", path, err) } @@ -156,7 +202,7 @@ func (u *Handler) IndexFile() http.Handler { })) } -func serveIndex(resp io.Writer, url string) error { +func serveRemote(resp io.Writer, url string) error { client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -168,6 +214,15 @@ func serveIndex(resp io.Writer, url string) error { } defer r.Body.Close() + if w, ok := resp.(http.ResponseWriter); ok { + for k, v := range r.Header { + for _, vv := range v { + w.Header().Add(k, vv) + } + } + w.WriteHeader(r.StatusCode) + } + _, err = io.Copy(resp, r.Body) return err }