diff --git a/go.mod b/go.mod index a17aa85..cc990f9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/rancher/steve -go 1.13 +go 1.16 replace ( github.com/crewjam/saml => github.com/rancher/saml v0.0.0-20180713225824-ce1532152fde diff --git a/pkg/ui/embed.go b/pkg/ui/embed.go new file mode 100644 index 0000000..19f3197 --- /dev/null +++ b/pkg/ui/embed.go @@ -0,0 +1,97 @@ +// +build embed + +package ui + +import ( + "embed" + "io" + "io/fs" + "net/http" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" +) + +// content holds our static web server content. +//go:embed ui/* +var staticContent embed.FS + +type fsFunc func(name string) (fs.File, error) + +func (f fsFunc) Open(name string) (fs.File, error) { + return f(name) +} + +func pathExist(path string) bool { + path = formatPath(path) + _, err := staticContent.Open(path) + return err == nil +} + +func openFile(path string) (fs.File, error) { + path = formatPath(path) + file, err := staticContent.Open(path) + if err != nil { + logrus.Errorf("openEmbedFile %s err: %v", path, err) + } + return file, err +} + +func formatPath(path string) string { + // To replace _nuxt/_cluster/... + // For embed, If a pattern names a directory, + // all files in the subtree rooted at that directory are embedded (recursively), + // except that files with names beginning with ‘.’ or ‘_’ are excluded. + return strings.ReplaceAll(path, "_", "") +} + +func serveEmbed(basePath string) http.Handler { + handler := fsFunc(func(name string) (fs.File, error) { + logrus.Debugf("serveEmbed name: %s", name) + assetPath := filepath.Join(basePath, name) + logrus.Debugf("serveEmbed final path: %s", assetPath) + return openFile(assetPath) + }) + + return http.FileServer(http.FS(handler)) +} + +func serveEmbedIndex(basePath string) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + path := filepath.Join(basePath, "dashboard", "index.html") + logrus.Debugf("serveEmbedIndex : %s", path) + f, _ := staticContent.Open(path) + io.Copy(rw, f) + f.Close() + }) +} + +func (u *Handler) ServeAsset() http.Handler { + return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + serveEmbed(u.pathSetting()).ServeHTTP(rw, req) + })) +} + +func (u *Handler) ServeFaviconDashboard() http.Handler { + return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + serveEmbed(filepath.Join(u.pathSetting(), "dashboard")).ServeHTTP(rw, req) + })) +} + +func (u *Handler) IndexFileOnNotFound() http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + path := filepath.Join(u.pathSetting(), req.URL.Path) + if pathExist(path) { + u.ServeAsset().ServeHTTP(rw, req) + } else { + u.IndexFile().ServeHTTP(rw, req) + } + }) +} + +func (u *Handler) IndexFile() http.Handler { + return u.indexMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + serveEmbedIndex(u.pathSetting()).ServeHTTP(rw, req) + })) +} diff --git a/pkg/ui/external.go b/pkg/ui/external.go new file mode 100644 index 0000000..1243a9b --- /dev/null +++ b/pkg/ui/external.go @@ -0,0 +1,42 @@ +// +build !embed + +package ui + +import ( + "net/http" + "os" + "path/filepath" +) + +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) + })) +} + +func (u *Handler) ServeFaviconDashboard() http.Handler { + return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + http.FileServer(http.Dir(filepath.Join(u.pathSetting(), "dashboard"))).ServeHTTP(rw, req) + })) +} + +func (u *Handler) IndexFileOnNotFound() http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + // we ignore directories here because we want those to come from the CDN when running in that mode + if stat, err := os.Stat(filepath.Join(u.pathSetting(), req.URL.Path)); err == nil && !stat.IsDir() { + u.ServeAsset().ServeHTTP(rw, req) + } else { + u.IndexFile().ServeHTTP(rw, req) + } + }) +} + +func (u *Handler) IndexFile() http.Handler { + return u.indexMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if path, isURL := u.path(); isURL { + _ = serveIndex(rw, path) + } else { + http.ServeFile(rw, req, filepath.Join(path, "index.html")) + } + })) +} diff --git a/pkg/ui/handler.go b/pkg/ui/handler.go index f630487..fc35f91 100644 --- a/pkg/ui/handler.go +++ b/pkg/ui/handler.go @@ -5,14 +5,16 @@ import ( "io" "io/ioutil" "net/http" - "os" - "path/filepath" "sync" "github.com/rancher/apiserver/pkg/middleware" "github.com/sirupsen/logrus" ) +const ( + defaultPath = "./ui" +) + var ( insecureClient = &http.Client{ Transport: &http.Transport{ @@ -24,10 +26,6 @@ var ( } ) -const ( - defaultPath = "./ui" -) - type StringSetting func() string type BoolSetting func() bool @@ -104,17 +102,6 @@ func NewUIHandler(opts *Options) *Handler { return h } -func (u *Handler) canDownload(url string) bool { - u.downloadOnce.Do(func() { - if err := serveIndex(ioutil.Discard, url); err == nil { - u.downloadSuccess = true - } else { - logrus.Errorf("Failed to download %s, falling back to packaged UI", url) - } - }) - return u.downloadSuccess -} - func (u *Handler) path() (path string, isURL bool) { switch u.offlineSetting() { case "dynamic": @@ -132,37 +119,15 @@ func (u *Handler) path() (path string, isURL bool) { } } -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) - })) -} - -func (u *Handler) ServeFaviconDashboard() http.Handler { - return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - http.FileServer(http.Dir(filepath.Join(u.pathSetting(), "dashboard"))).ServeHTTP(rw, req) - })) -} - -func (u *Handler) IndexFileOnNotFound() http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - // we ignore directories here because we want those to come from the CDN when running in that mode - if stat, err := os.Stat(filepath.Join(u.pathSetting(), req.URL.Path)); err == nil && !stat.IsDir() { - u.ServeAsset().ServeHTTP(rw, req) +func (u *Handler) canDownload(url string) bool { + u.downloadOnce.Do(func() { + if err := serveIndex(ioutil.Discard, url); err == nil { + u.downloadSuccess = true } else { - u.IndexFile().ServeHTTP(rw, req) + logrus.Errorf("Failed to download %s, falling back to packaged UI", url) } }) -} - -func (u *Handler) IndexFile() http.Handler { - return u.indexMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if path, isURL := u.path(); isURL { - _ = serveIndex(rw, path) - } else { - http.ServeFile(rw, req, filepath.Join(path, "index.html")) - } - })) + return u.downloadSuccess } func serveIndex(resp io.Writer, url string) error { diff --git a/pkg/ui/routes.go b/pkg/ui/routes.go index afa7a4f..b8ea5db 100644 --- a/pkg/ui/routes.go +++ b/pkg/ui/routes.go @@ -5,10 +5,7 @@ import ( "strings" "github.com/gorilla/mux" -) - -var ( - UIOffline = "dynamic" + "github.com/rancher/steve/pkg/version" ) func New(path string) http.Handler { @@ -20,7 +17,13 @@ func New(path string) http.Handler { return path }, Offline: func() string { - return UIOffline + if path != "" { + return "true" + } + return "dynamic" + }, + ReleaseSetting: func() bool { + return version.IsRelease() }, }) diff --git a/pkg/version/version.go b/pkg/version/version.go index bfbfa54..f18e206 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,12 +1,23 @@ package version -import "fmt" +import ( + "fmt" + "regexp" + "strings" +) var ( Version = "dev" GitCommit = "HEAD" + + // K-EXPLORER + releasePattern = regexp.MustCompile("^v[0-9]") ) func FriendlyVersion() string { return fmt.Sprintf("%s (%s)", Version, GitCommit) } + +func IsRelease() bool { + return !strings.Contains(Version, "dev") && releasePattern.MatchString(Version) +}