mirror of
https://github.com/niusmallnan/steve.git
synced 2025-08-12 18:45:18 +00:00
K-EXPLORER: Merge branch 'ke/v0.1' into ke/v0.2
This commit is contained in:
commit
272322312a
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/rancher/steve
|
module github.com/rancher/steve
|
||||||
|
|
||||||
go 1.13
|
go 1.16
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/crewjam/saml => github.com/rancher/saml v0.0.0-20180713225824-ce1532152fde
|
github.com/crewjam/saml => github.com/rancher/saml v0.0.0-20180713225824-ce1532152fde
|
||||||
|
@ -3,12 +3,14 @@ package cluster
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rancher/apiserver/pkg/store/empty"
|
"github.com/rancher/apiserver/pkg/store/empty"
|
||||||
"github.com/rancher/apiserver/pkg/types"
|
"github.com/rancher/apiserver/pkg/types"
|
||||||
detector "github.com/rancher/kubernetes-provider-detector"
|
detector "github.com/rancher/kubernetes-provider-detector"
|
||||||
"github.com/rancher/steve/pkg/accesscontrol"
|
"github.com/rancher/steve/pkg/accesscontrol"
|
||||||
"github.com/rancher/steve/pkg/attributes"
|
"github.com/rancher/steve/pkg/attributes"
|
||||||
|
"github.com/rancher/steve/pkg/podimpersonation"
|
||||||
steveschema "github.com/rancher/steve/pkg/schema"
|
steveschema "github.com/rancher/steve/pkg/schema"
|
||||||
"github.com/rancher/steve/pkg/stores/proxy"
|
"github.com/rancher/steve/pkg/stores/proxy"
|
||||||
"github.com/rancher/wrangler/pkg/genericcondition"
|
"github.com/rancher/wrangler/pkg/genericcondition"
|
||||||
@ -19,7 +21,19 @@ import (
|
|||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
shellPodImage = "rancher/shell:v0.1.6"
|
||||||
|
shellPodNS = "kube-system"
|
||||||
|
)
|
||||||
|
|
||||||
func Register(ctx context.Context, apiSchemas *types.APISchemas, cg proxy.ClientGetter, schemaFactory steveschema.Factory) {
|
func Register(ctx context.Context, apiSchemas *types.APISchemas, cg proxy.ClientGetter, schemaFactory steveschema.Factory) {
|
||||||
|
// K-EXPLORER
|
||||||
|
shell := &shell{
|
||||||
|
cg: cg,
|
||||||
|
namespace: shellPodNS,
|
||||||
|
impersonator: podimpersonation.New("shell", cg, time.Hour, func() string { return shellPodImage }),
|
||||||
|
}
|
||||||
|
|
||||||
apiSchemas.InternalSchemas.TypeName("management.cattle.io.cluster", Cluster{})
|
apiSchemas.InternalSchemas.TypeName("management.cattle.io.cluster", Cluster{})
|
||||||
|
|
||||||
apiSchemas.MustImportAndCustomize(&ApplyInput{}, nil)
|
apiSchemas.MustImportAndCustomize(&ApplyInput{}, nil)
|
||||||
@ -57,6 +71,10 @@ func Register(ctx context.Context, apiSchemas *types.APISchemas, cg proxy.Client
|
|||||||
Output: "applyOutput",
|
Output: "applyOutput",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// K-EXPLORER
|
||||||
|
schema.LinkHandlers = map[string]http.Handler{
|
||||||
|
"shell": shell,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
148
pkg/resources/cluster/shell.go
Normal file
148
pkg/resources/cluster/shell.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rancher/steve/pkg/podimpersonation"
|
||||||
|
"github.com/rancher/steve/pkg/stores/proxy"
|
||||||
|
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type shell struct {
|
||||||
|
namespace string
|
||||||
|
impersonator *podimpersonation.PodImpersonation
|
||||||
|
cg proxy.ClientGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shell) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx, user, client, err := s.contextAndClient(req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pod, err := s.impersonator.CreatePod(ctx, user, s.createPod(), &podimpersonation.PodOptions{
|
||||||
|
Wait: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||||
|
defer cancel()
|
||||||
|
_ = client.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{})
|
||||||
|
}()
|
||||||
|
s.proxyRequest(rw, req, pod, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shell) proxyRequest(rw http.ResponseWriter, req *http.Request, pod *v1.Pod, client kubernetes.Interface) {
|
||||||
|
attachURL := client.CoreV1().RESTClient().
|
||||||
|
Get().
|
||||||
|
Namespace(pod.Namespace).
|
||||||
|
Resource("pods").
|
||||||
|
Name(pod.Name).
|
||||||
|
SubResource("exec").
|
||||||
|
VersionedParams(&v1.PodExecOptions{
|
||||||
|
Stdin: true,
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
TTY: true,
|
||||||
|
Container: "shell",
|
||||||
|
Command: []string{"welcome"},
|
||||||
|
}, scheme.ParameterCodec).URL()
|
||||||
|
|
||||||
|
httpClient := client.CoreV1().RESTClient().(*rest.RESTClient).Client
|
||||||
|
p := httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL = attachURL
|
||||||
|
req.Host = attachURL.Host
|
||||||
|
delete(req.Header, "Impersonate-Group")
|
||||||
|
delete(req.Header, "Impersonate-User")
|
||||||
|
delete(req.Header, "Authorization")
|
||||||
|
delete(req.Header, "Cookie")
|
||||||
|
},
|
||||||
|
Transport: httpClient.Transport,
|
||||||
|
FlushInterval: time.Millisecond * 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shell) contextAndClient(req *http.Request) (context.Context, user.Info, kubernetes.Interface, error) {
|
||||||
|
ctx := req.Context()
|
||||||
|
client, err := s.cg.AdminK8sInterface()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := request.UserFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, nil, nil, validation.Unauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx, user, client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shell) createPod() *v1.Pod {
|
||||||
|
return &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "dashboard-shell-",
|
||||||
|
Namespace: s.namespace,
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
TerminationGracePeriodSeconds: new(int64),
|
||||||
|
RestartPolicy: v1.RestartPolicyNever,
|
||||||
|
NodeSelector: map[string]string{
|
||||||
|
"kubernetes.io/os": "linux",
|
||||||
|
},
|
||||||
|
Tolerations: []v1.Toleration{
|
||||||
|
{
|
||||||
|
Key: "cattle.io/os",
|
||||||
|
Operator: "Equal",
|
||||||
|
Value: "linux",
|
||||||
|
Effect: "NoSchedule",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "node-role.kubernetes.io/controlplane",
|
||||||
|
Operator: "Equal",
|
||||||
|
Value: "true",
|
||||||
|
Effect: "NoSchedule",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "node-role.kubernetes.io/etcd",
|
||||||
|
Operator: "Equal",
|
||||||
|
Value: "true",
|
||||||
|
Effect: "NoExecute",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Name: "shell",
|
||||||
|
TTY: true,
|
||||||
|
Stdin: true,
|
||||||
|
StdinOnce: true,
|
||||||
|
Env: []v1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: "KUBECONFIG",
|
||||||
|
Value: "/home/shell/.kube/config",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Image: shellPodImage,
|
||||||
|
ImagePullPolicy: v1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -48,9 +48,9 @@ func New(cfg *rest.Config, sf schema.Factory, authMiddleware auth.Middleware, ne
|
|||||||
APIRoot: w(a.apiHandler(apiRoot)),
|
APIRoot: w(a.apiHandler(apiRoot)),
|
||||||
}
|
}
|
||||||
if routerFunc == nil {
|
if routerFunc == nil {
|
||||||
return a.server, router.Routes(handlers), nil
|
return a.server, rewriteLocalCluster(router.Routes(handlers)), nil
|
||||||
}
|
}
|
||||||
return a.server, routerFunc(handlers), nil
|
return a.server, rewriteLocalCluster(routerFunc(handlers)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiServer struct {
|
type apiServer struct {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/rancher/apiserver/pkg/types"
|
"github.com/rancher/apiserver/pkg/types"
|
||||||
"github.com/rancher/steve/pkg/attributes"
|
"github.com/rancher/steve/pkg/attributes"
|
||||||
@ -35,3 +38,15 @@ func k8sAPI(sf schema.Factory, apiOp *types.APIRequest) {
|
|||||||
func apiRoot(sf schema.Factory, apiOp *types.APIRequest) {
|
func apiRoot(sf schema.Factory, apiOp *types.APIRequest) {
|
||||||
apiOp.Type = "apiRoot"
|
apiOp.Type = "apiRoot"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rewriteLocalCluster(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if strings.HasPrefix(req.URL.Path, "/k8s/clusters/local") {
|
||||||
|
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/k8s/clusters/local")
|
||||||
|
if req.URL.Path == "" {
|
||||||
|
req.URL.Path = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
97
pkg/ui/embed.go
Normal file
97
pkg/ui/embed.go
Normal file
@ -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)
|
||||||
|
}))
|
||||||
|
}
|
42
pkg/ui/external.go
Normal file
42
pkg/ui/external.go
Normal file
@ -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"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
@ -5,14 +5,16 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/rancher/apiserver/pkg/middleware"
|
"github.com/rancher/apiserver/pkg/middleware"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPath = "./ui"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
insecureClient = &http.Client{
|
insecureClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
@ -24,10 +26,6 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultPath = "./ui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StringSetting func() string
|
type StringSetting func() string
|
||||||
type BoolSetting func() bool
|
type BoolSetting func() bool
|
||||||
|
|
||||||
@ -104,17 +102,6 @@ func NewUIHandler(opts *Options) *Handler {
|
|||||||
return h
|
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) {
|
func (u *Handler) path() (path string, isURL bool) {
|
||||||
switch u.offlineSetting() {
|
switch u.offlineSetting() {
|
||||||
case "dynamic":
|
case "dynamic":
|
||||||
@ -132,37 +119,15 @@ func (u *Handler) path() (path string, isURL bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Handler) ServeAsset() http.Handler {
|
func (u *Handler) canDownload(url string) bool {
|
||||||
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
u.downloadOnce.Do(func() {
|
||||||
http.FileServer(http.Dir(u.pathSetting())).ServeHTTP(rw, req)
|
if err := serveIndex(ioutil.Discard, url); err == nil {
|
||||||
}))
|
u.downloadSuccess = true
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
u.IndexFile().ServeHTTP(rw, req)
|
logrus.Errorf("Failed to download %s, falling back to packaged UI", url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
return u.downloadSuccess
|
||||||
|
|
||||||
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"))
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveIndex(resp io.Writer, url string) error {
|
func serveIndex(resp io.Writer, url string) error {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rancher/steve/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(path string) http.Handler {
|
func New(path string) http.Handler {
|
||||||
@ -15,6 +16,15 @@ func New(path string) http.Handler {
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
},
|
},
|
||||||
|
Offline: func() string {
|
||||||
|
if path != "" {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "dynamic"
|
||||||
|
},
|
||||||
|
ReleaseSetting: func() bool {
|
||||||
|
return version.IsRelease()
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
package version
|
package version
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "dev"
|
Version = "dev"
|
||||||
GitCommit = "HEAD"
|
GitCommit = "HEAD"
|
||||||
|
|
||||||
|
// K-EXPLORER
|
||||||
|
releasePattern = regexp.MustCompile("^v[0-9]")
|
||||||
)
|
)
|
||||||
|
|
||||||
func FriendlyVersion() string {
|
func FriendlyVersion() string {
|
||||||
return fmt.Sprintf("%s (%s)", Version, GitCommit)
|
return fmt.Sprintf("%s (%s)", Version, GitCommit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsRelease() bool {
|
||||||
|
return !strings.Contains(Version, "dev") && releasePattern.MatchString(Version)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user