diff --git a/Makefile b/Makefile index 9463831d53..7228b866a0 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,9 @@ cleangen: @rm -f ${GENERATED_CODE} dev: BUILD_TAGS+=dev +dev: BUILD_TAGS+=ui dev: build-ui-ifne - @echo "==> Building Watchtower with dev features enabled" + @echo "==> Building Watchtower with dev and UI features enabled" @CGO_ENABLED=$(CGO_ENABLED) BUILD_TAGS='$(BUILD_TAGS)' WATCHTOWER_DEV_BUILD=1 sh -c "'$(CURDIR)/scripts/build.sh'" build-ui: diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 9ba2daf53e..5f392459fc 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/watchtower/internal/servers/controller/handlers/projects" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/roles" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/users" - "github.com/hashicorp/watchtower/internal/ui" ) type HandlerProperties struct { @@ -51,63 +50,6 @@ func (c *Controller) handler(props HandlerProperties) (http.Handler, error) { return commonWrappedHandler, nil } -func handleUi(c *Controller) http.Handler { - var nextHandler http.Handler - if c.conf.RawConfig.PassthroughDirectory != "" { - nextHandler = ui.DevPassthroughHandler(c.logger, c.conf.RawConfig.PassthroughDirectory) - } else { - nextHandler = http.FileServer(ui.AssetFile()) - } - - rootHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/": - irw := newIndexResponseWriter(c.conf.DefaultOrgId) - nextHandler.ServeHTTP(irw, r) - irw.writeToWriter(w) - - default: - nextHandler.ServeHTTP(w, r) - } - }) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - dotIndex := strings.LastIndex(r.URL.Path, ".") - switch dotIndex { - case -1: - // For all paths without an extension serve /index.html - r.URL.Path = "/" - - default: - switch r.URL.Path { - case "/", "/favicon.png", "/assets/styles.css": - - default: - for i := dotIndex + 1; i < len(r.URL.Path); i++ { - intVal := r.URL.Path[i] - // Current guidance from FE is if it's only alphanum after - // the last dot, treat it as an extension - if intVal < '0' || - (intVal > '9' && intVal < 'A') || - (intVal > 'Z' && intVal < 'a') || - intVal > 'z' { - // Not an extension. Serve the contents of index.html - r.URL.Path = "/" - } - } - } - } - - // Fall through to the next handler - rootHandler.ServeHTTP(w, r) - }) -} - func handleGrpcGateway(c *Controller) (http.Handler, error) { // Register*ServiceHandlerServer methods ignore the passed in ctx. Using the baseContext now just in case this changes // in the future, at which point we'll want to be using the baseContext. diff --git a/internal/ui/passthrough.go b/internal/servers/controller/handler_noui.go similarity index 56% rename from internal/ui/passthrough.go rename to internal/servers/controller/handler_noui.go index 3ad4237f3e..292baea446 100644 --- a/internal/ui/passthrough.go +++ b/internal/servers/controller/handler_noui.go @@ -1,4 +1,4 @@ -package ui +package controller import ( "net/http" @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/go-hclog" ) -func DevPassthroughHandler(logger hclog.Logger, passthroughDir string) http.Handler { +func devPassthroughHandler(logger hclog.Logger, passthroughDir string) http.Handler { // Panic may not be ideal but this is never a production call and it'll // panic on startup. We could also just change the function to return // an error. @@ -21,3 +21,12 @@ func DevPassthroughHandler(logger hclog.Logger, passthroughDir string) http.Hand return prefixHandler } + +var handleUi = func(c *Controller) http.Handler { + if c.conf.RawConfig.PassthroughDirectory != "" { + return devPassthroughHandler(c.logger, c.conf.RawConfig.PassthroughDirectory) + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + }) +} diff --git a/internal/servers/controller/handler_ui.go b/internal/servers/controller/handler_ui.go new file mode 100644 index 0000000000..78340be718 --- /dev/null +++ b/internal/servers/controller/handler_ui.go @@ -0,0 +1,71 @@ +// +build ui + +package controller + +import ( + "net/http" + "strings" + + "github.com/hashicorp/watchtower/internal/ui" +) + +func init() { + handleUi = handleUiWithAssets +} + +func handleUiWithAssets(c *Controller) http.Handler { + var nextHandler http.Handler + if c.conf.RawConfig.PassthroughDirectory != "" { + nextHandler = devPassthroughHandler(c.logger, c.conf.RawConfig.PassthroughDirectory) + } else { + nextHandler = http.FileServer(ui.AssetFile()) + } + + rootHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/": + irw := newIndexResponseWriter(c.conf.DefaultOrgId) + nextHandler.ServeHTTP(irw, r) + irw.writeToWriter(w) + + default: + nextHandler.ServeHTTP(w, r) + } + }) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + dotIndex := strings.LastIndex(r.URL.Path, ".") + switch dotIndex { + case -1: + // For all paths without an extension serve /index.html + r.URL.Path = "/" + + default: + switch r.URL.Path { + case "/", "/favicon.png", "/assets/styles.css": + + default: + for i := dotIndex + 1; i < len(r.URL.Path); i++ { + intVal := r.URL.Path[i] + // Current guidance from FE is if it's only alphanum after + // the last dot, treat it as an extension + if intVal < '0' || + (intVal > '9' && intVal < 'A') || + (intVal > 'Z' && intVal < 'a') || + intVal > 'z' { + // Not an extension. Serve the contents of index.html + r.URL.Path = "/" + } + } + } + } + + // Fall through to the next handler + rootHandler.ServeHTTP(w, r) + }) +}