diff --git a/pkg/assets/apiserver/asset_apiserver.go b/pkg/assets/apiserver/asset_apiserver.go
index 8eb62697a8..b62140a8f0 100644
--- a/pkg/assets/apiserver/asset_apiserver.go
+++ b/pkg/assets/apiserver/asset_apiserver.go
@@ -129,13 +129,6 @@ func (c completedAssetServerConfig) New(delegationTarget genericapiserver.Delega
if err := c.addAssets(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
return nil, err
}
- if err := c.addExtensionScripts(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
- return nil, err
- }
- if err := c.addExtensionStyleSheets(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
- return nil, err
- }
- c.addExtensionFiles(s.GenericAPIServer.Handler.NonGoRestfulMux)
if err := c.addWebConsoleConfig(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
return nil, err
}
@@ -173,41 +166,6 @@ func (c completedAssetServerConfig) addAssets(serverMux *genericmux.PathRecorder
return nil
}
-func (c completedAssetServerConfig) addExtensionScripts(serverMux *genericmux.PathRecorderMux) error {
- // Extension scripts
- extScriptsPath := path.Join(c.PublicURL.Path, "scripts/extensions.js")
- extScriptsHandler, err := assets.ExtensionScriptsHandler(c.Options.ExtensionScripts, c.Options.ExtensionDevelopment)
- if err != nil {
- return err
- }
- extScriptsHandler = assets.SecurityHeadersHandler(extScriptsHandler)
- serverMux.UnlistedHandle(extScriptsPath, assets.GzipHandler(extScriptsHandler))
- return nil
-}
-
-func (c completedAssetServerConfig) addExtensionStyleSheets(serverMux *genericmux.PathRecorderMux) error {
- // Extension stylesheets
- extStylesheetsPath := path.Join(c.PublicURL.Path, "styles/extensions.css")
- extStylesheetsHandler, err := assets.ExtensionStylesheetsHandler(c.Options.ExtensionStylesheets, c.Options.ExtensionDevelopment)
- if err != nil {
- return err
- }
- extStylesheetsHandler = assets.SecurityHeadersHandler(extStylesheetsHandler)
- serverMux.UnlistedHandle(extStylesheetsPath, assets.GzipHandler(extStylesheetsHandler))
- return nil
-}
-
-func (c completedAssetServerConfig) addExtensionFiles(serverMux *genericmux.PathRecorderMux) {
- // Extension files
- for _, extConfig := range c.Options.Extensions {
- extBasePath := path.Join(c.PublicURL.Path, "extensions", extConfig.Name)
- extPath := extBasePath + "/"
- extHandler := assets.AssetExtensionHandler(extConfig.SourceDirectory, extPath, extConfig.HTML5Mode)
- serverMux.UnlistedHandlePrefix(extPath, http.StripPrefix(extBasePath, extHandler))
- serverMux.UnlistedHandle(extBasePath, http.RedirectHandler(extPath, http.StatusMovedPermanently))
- }
-}
-
func (c *completedAssetServerConfig) addWebConsoleConfig(serverMux *genericmux.PathRecorderMux) error {
masterURL, err := url.Parse(c.Options.MasterPublicURL)
if err != nil {
@@ -266,7 +224,7 @@ func (c completedAssetServerConfig) buildAssetHandler() (http.Handler, error) {
}
var err error
- handler, err = assets.HTML5ModeHandler(c.PublicURL.Path, subcontextMap, handler, assetFunc)
+ handler, err = assets.HTML5ModeHandler(c.PublicURL.Path, subcontextMap, c.Options.ExtensionScripts, c.Options.ExtensionStylesheets, handler, assetFunc)
if err != nil {
return nil, err
}
diff --git a/pkg/assets/extensions.go b/pkg/assets/extensions.go
deleted file mode 100644
index 372bd27f41..0000000000
--- a/pkg/assets/extensions.go
+++ /dev/null
@@ -1,186 +0,0 @@
-package assets
-
-import (
- "bytes"
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "path/filepath"
- "strings"
-
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
-)
-
-// ExtensionScriptsHandler concatenates and serves extension JavaScript files as one HTTP response.
-func ExtensionScriptsHandler(files []string, developmentMode bool) (http.Handler, error) {
- return concatHandler(files, developmentMode, "application/javascript; charset=utf-8", ";\n")
-}
-
-// ExtensionStylesheetsHandler concatenates and serves extension stylesheets as one HTTP response.
-func ExtensionStylesheetsHandler(files []string, developmentMode bool) (http.Handler, error) {
- return concatHandler(files, developmentMode, "text/css; charset=utf-8", "\n")
-}
-
-func concatHandler(files []string, developmentMode bool, mediaType, separator string) (http.Handler, error) {
- // Read the files for each request if development mode is enabled.
- if developmentMode {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- bytes, err := concatAll(files, separator)
- if err != nil {
- utilruntime.HandleError(fmt.Errorf("Error serving extension content: %v", err))
- http.Error(w, "Internal server error", http.StatusInternalServerError)
- }
- serve(w, r, bytes, mediaType, "")
- }), nil
- }
-
- // Otherwise, read the files once on server startup.
- bytes, err := concatAll(files, separator)
- if err != nil {
- return nil, err
- }
- hash := calculateMD5(bytes)
-
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- serve(w, r, bytes, mediaType, hash)
- }), nil
-}
-
-func concatAll(files []string, separator string) ([]byte, error) {
- var buffer bytes.Buffer
- for _, file := range files {
- f, err := os.Open(file)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- _, err = buffer.ReadFrom(f)
- if err != nil {
- return nil, err
- }
-
- _, err = buffer.WriteString(separator)
- if err != nil {
- return nil, err
- }
- }
-
- return buffer.Bytes(), nil
-}
-
-func calculateMD5(bytes []byte) string {
- hasher := md5.New()
- hasher.Write(bytes)
- sum := hasher.Sum(nil)
-
- return hex.EncodeToString(sum)
-}
-
-func generateETag(w http.ResponseWriter, r *http.Request, hash string) string {
- vary := w.Header().Get("Vary")
- varyHeaders := []string{}
- if vary != "" {
- varyHeaders = varyHeaderRegexp.Split(vary, -1)
- }
-
- varyHeaderValues := ""
- for _, varyHeader := range varyHeaders {
- varyHeaderValues += r.Header.Get(varyHeader)
- }
-
- return fmt.Sprintf("W/\"%s_%s\"", hash, hex.EncodeToString([]byte(varyHeaderValues)))
-}
-
-func serve(w http.ResponseWriter, r *http.Request, bytes []byte, mediaType, hash string) {
- if len(hash) > 0 {
- etag := generateETag(w, r, hash)
- if r.Header.Get("If-None-Match") == etag {
- w.WriteHeader(http.StatusNotModified)
- return
- }
-
- w.Header().Set("ETag", etag)
- w.Header().Set("Cache-Control", "public, max-age=0, must-revalidate")
- } else {
- w.Header().Add("Cache-Control", "no-cache, no-store")
- }
-
- w.Header().Set("Content-Type", mediaType)
- _, err := w.Write(bytes)
- if err != nil {
- utilruntime.HandleError(fmt.Errorf("Error serving extension content: %v", err))
- }
-}
-
-// AssetExtensionHandler serves extension files from sourceDir. context is the URL context for this
-// extension.
-func AssetExtensionHandler(sourceDir, context string, html5Mode bool) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- serveExtensionFile(w, r, sourceDir, context, html5Mode)
- })
-}
-
-// Injects the HTML into the file and serves it.
-func serveIndex(w http.ResponseWriter, r *http.Request, path, base string) {
- content, err := ioutil.ReadFile(path)
- if err != nil {
- http.NotFound(w, r)
- return
- }
-
- // Make sure the base always ends in a trailing slash.
- if !strings.HasSuffix(base, "/") {
- base += "/"
- }
- content = bytes.Replace(content, []byte(``), []byte(fmt.Sprintf(``, base)), 1)
-
- w.Header().Add("Cache-Control", "no-cache, no-store")
- w.Header().Set("Content-Type", "text/html; charset=UTF-8")
-
- w.Write(content)
-}
-
-func serveFile(w http.ResponseWriter, r *http.Request, path, base string, html5Mode bool) {
- _, name := filepath.Split(path)
- if html5Mode && name == "index.html" {
- // Inject the correct base for Angular apps if the file is index.html.
- serveIndex(w, r, path, base)
- } else {
- // Otherwise just serve the file.
- http.ServeFile(w, r, path)
- }
-}
-
-// Serve the extension file under dir matching the path from the request URI.
-func serveExtensionFile(w http.ResponseWriter, r *http.Request, sourceDir, context string, html5Mode bool) {
- // The path to the requested file on the filesystem.
- file := filepath.Join(sourceDir, r.URL.Path)
-
- if html5Mode {
- // Check if the file exists.
- fileInfo, err := os.Stat(file)
- if err != nil {
- if os.IsNotExist(err) {
- index := filepath.Join(sourceDir, "index.html")
- serveFile(w, r, index, context, html5Mode)
- return
- }
-
- utilruntime.HandleError(fmt.Errorf("Error serving extension file: %v", err))
- http.Error(w, "Internal server error", http.StatusInternalServerError)
- return
- }
-
- if fileInfo.IsDir() {
- index := filepath.Join(sourceDir, "index.html")
- serveFile(w, r, index, context, html5Mode)
- return
- }
- }
-
- serveFile(w, r, file, context, html5Mode)
-}
diff --git a/pkg/assets/handlers.go b/pkg/assets/handlers.go
index 8afce24220..e15f16c5a1 100644
--- a/pkg/assets/handlers.go
+++ b/pkg/assets/handlers.go
@@ -5,6 +5,7 @@ import (
"compress/gzip"
"encoding/hex"
"fmt"
+ "html"
"io"
"net/http"
"path"
@@ -112,7 +113,7 @@ func (s LongestToShortest) Less(i, j int) bool {
//
// subcontextMap is a map of keys (subcontexts, no leading or trailing slashes) to the asset path (no
// leading slash) to serve for that subcontext if a resource that does not exist is requested
-func HTML5ModeHandler(contextRoot string, subcontextMap map[string]string, h http.Handler, getAsset AssetFunc) (http.Handler, error) {
+func HTML5ModeHandler(contextRoot string, subcontextMap map[string]string, extensionScripts []string, extensionStylesheets []string, h http.Handler, getAsset AssetFunc) (http.Handler, error) {
subcontextData := map[string][]byte{}
subcontexts := []string{}
@@ -127,6 +128,17 @@ func HTML5ModeHandler(contextRoot string, subcontextMap map[string]string, h htt
base += "/"
}
b = bytes.Replace(b, []byte(``), []byte(fmt.Sprintf(``, base)), 1)
+
+ // Inject extension scripts and stylesheets, but only for the console itself, which has an empty subcontext
+ if len(subcontext) == 0 {
+ if len(extensionScripts) > 0 {
+ b = addExtensionScripts(b, extensionScripts)
+ }
+ if len(extensionStylesheets) > 0 {
+ b = addExtensionStylesheets(b, extensionStylesheets)
+ }
+ }
+
subcontextData[subcontext] = b
subcontexts = append(subcontexts, subcontext)
}
@@ -153,6 +165,30 @@ func HTML5ModeHandler(contextRoot string, subcontextMap map[string]string, h htt
}), nil
}
+// Add the extension scripts as the last scripts, just before the body closing tag.
+func addExtensionScripts(content []byte, extensionScripts []string) []byte {
+ var scriptTags bytes.Buffer
+ for _, scriptURL := range extensionScripts {
+ scriptTags.WriteString(fmt.Sprintf("\n", html.EscapeString(scriptURL)))
+ }
+
+ replaceBefore := []byte("