Skip to content

Commit

Permalink
feat(gui): add disabled in the index.html and remove disabled page (#…
Browse files Browse the repository at this point in the history
…8813)

Goal is to move this logic inside the GUI in kumahq/kuma-gui#467

Signed-off-by: Charly Molter <[email protected]>
  • Loading branch information
lahabana authored Jan 16, 2024
1 parent 49b9504 commit ce45423
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 188 deletions.
67 changes: 20 additions & 47 deletions pkg/api-server/gui_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,6 @@ import (
"github.com/kumahq/kuma/app/kuma-ui/pkg/resources"
)

var disabledPage = `
<!DOCTYPE html><html lang=en>
<head>
<style>
.center {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
border: 3px solid green;
}
</style>
</head>
<body>
<div class="center"><strong>
GUI is disabled. If this is a Zone CP, please check the GUI on the Global CP.
If this isn't a Zone CP the GUI can be enabled by setting the configuration KUMA_API_SERVER_GUI_ENABLED=true.
</strong></div>
</body>
</html>
`

type GuiConfig struct {
ApiUrl string `json:"apiUrl"`
BaseGuiPath string `json:"baseGuiPath"`
Expand All @@ -44,31 +22,26 @@ type GuiConfig struct {
ReadOnly bool `json:"apiReadOnly"`
}

func NewGuiHandler(guiPath string, enabledGui bool, guiConfig GuiConfig) (http.Handler, error) {
if enabledGui {
guiFs := resources.GuiFS()
tmpl := template.Must(template.New("index.html").Funcs(sprig.HtmlFuncMap()).ParseFS(guiFs, "index.html"))
buf := bytes.Buffer{}
err := tmpl.Execute(&buf, guiConfig)
if err != nil {
return nil, err
}
return http.StripPrefix(guiPath, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
_, err := guiFs.Open(request.URL.Path)
if err == nil {
http.FileServer(http.FS(guiFs)).ServeHTTP(writer, request)
return
}
writer.WriteHeader(http.StatusOK)
writer.Header().Set("Content-Type", "text/html")
_, _ = writer.Write(buf.Bytes())
})), nil
func NewGuiHandler(guiPath string, disableGui bool, guiConfig GuiConfig) (http.Handler, error) {
guiFs := resources.GuiFS()
f := "index.html"
if disableGui {
f = "no-gui.html"
}
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusOK)
_, err := writer.Write([]byte(disabledPage))
if err != nil {
log.Error(err, "could not write the response")
tmpl := template.Must(template.New(f).Funcs(sprig.HtmlFuncMap()).ParseFS(guiFs, f))
buf := bytes.Buffer{}
err := tmpl.Execute(&buf, guiConfig)
if err != nil {
return nil, err
}
return http.StripPrefix(guiPath, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
_, err := guiFs.Open(request.URL.Path)
if err == nil {
http.FileServer(http.FS(guiFs)).ServeHTTP(writer, request)
return
}
}), nil
writer.WriteHeader(http.StatusOK)
writer.Header().Set("Content-Type", "text/html")
_, _ = writer.Write(buf.Bytes())
})), nil
}
235 changes: 104 additions & 131 deletions pkg/api-server/gui_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,140 +18,113 @@ import (
var _ = Describe("GUI Server", func() {
var baseUrl string

Describe("enabled", func() {
type testCase struct {
urlPath string
expectedFile string
basePath string
guiRootUrl string
}
DescribeTable("should expose file", func(given testCase) {
// given
var apiServer *api_server.ApiServer
var stop func()
apiServer, _, stop = StartApiServer(NewTestApiServerConfigurer().WithConfigMutator(func(config *server.ApiServerConfig) {
config.GUI.Enabled = true
config.RootUrl = "https://foo.bar.com:8080/foo"
if given.basePath != "" {
config.GUI.BasePath = given.basePath
}
if given.guiRootUrl != "" {
config.GUI.RootUrl = given.guiRootUrl
}
}))
baseUrl = "http://" + apiServer.Address()
defer stop()

// when
resp, err := http.Get(fmt.Sprintf("%s%s", baseUrl, given.urlPath))
// then
Expect(err).ToNot(HaveOccurred())

// when
received, err := io.ReadAll(resp.Body)

// then
Expect(resp.Body.Close()).To(Succeed())
Expect(err).ToNot(HaveOccurred())

Expect(received).To(WithTransform(func(in []byte) []byte {
// Remove the part of the file name that changes always
r := regexp.MustCompile(`index[\-\.][a-z0-9]+\.`).ReplaceAll(in, []byte("index."))
r = regexp.MustCompile(`"version":"[^"]*"`).ReplaceAll(r, []byte(`"version":"0.0.0"`))
if r[len(r)-1] != '\n' {
r = append(r, '\n')
}
return r
}, matchers.MatchGoldenEqual(given.expectedFile)))
},
Entry("should serve index.html without path", testCase{
urlPath: "/gui",
expectedFile: filepath.Join("testdata", "index.html"),
}),
Entry("should serve robots.txt correctly", testCase{
urlPath: "/gui/robots.txt",
expectedFile: filepath.Join("testdata", "robots.txt"),
}),
Entry("should serve on different path", testCase{
urlPath: "/gui/meshes",
expectedFile: filepath.Join("testdata", "gui_other_files.html"),
}),
Entry("should serve index.html with / path", testCase{
urlPath: "/gui/",
expectedFile: filepath.Join("testdata", "index.html"),
}),
Entry("should serve index.html on alternative path", testCase{
urlPath: "/ui/foo",
expectedFile: filepath.Join("testdata", "gui_with_base_path.html"),
basePath: "/ui",
}),
Entry("should serve index.html on alternative path with end /", testCase{
urlPath: "/ui/",
expectedFile: filepath.Join("testdata", "gui_with_base_path_with_slash.html"),
basePath: "/ui/",
}),
Entry("should serve index.html with path from rootUrl", testCase{
urlPath: "/gui/",
expectedFile: filepath.Join("testdata", "gui_with_root_url.html"),
guiRootUrl: "https://foo.com/gui/foo",
}),
Entry("should serve index.html with path from rootUrl even with basePath set", testCase{
urlPath: "/foo/",
expectedFile: filepath.Join("testdata", "gui_with_root_url_and_base_path.html"),
basePath: "/foo",
guiRootUrl: "https://foo.com/gui/foo",
}),
Entry("should serve index.html on alternative path", testCase{
urlPath: "/ui/foo",
expectedFile: filepath.Join("testdata", "gui_with_base_path.html"),
basePath: "/ui",
}),
)
})

Describe("disabled",
func() {
type testCase struct {
urlPath string
expected string
type testCase struct {
urlPath string
expectedFile string
basePath string
guiRootUrl string
disabled bool
}
DescribeTable("should expose file", func(given testCase) {
// given
var apiServer *api_server.ApiServer
var stop func()
apiServer, _, stop = StartApiServer(NewTestApiServerConfigurer().WithConfigMutator(func(config *server.ApiServerConfig) {
config.GUI.Enabled = !given.disabled
config.RootUrl = "https://foo.bar.com:8080/foo"
if given.basePath != "" {
config.GUI.BasePath = given.basePath
}
DescribeTable("should not expose file", func(given testCase) {
// given
var apiServer *api_server.ApiServer
var stop func()
apiServer, _, stop = StartApiServer(NewTestApiServerConfigurer())
baseUrl = "http://" + apiServer.Address()
defer stop()

// when
resp, err := http.Get(fmt.Sprintf("%s%s", baseUrl, given.urlPath))
if given.guiRootUrl != "" {
config.GUI.RootUrl = given.guiRootUrl
}
}))
baseUrl = "http://" + apiServer.Address()
defer stop()

// then
Expect(err).ToNot(HaveOccurred())
// when
resp, err := http.Get(fmt.Sprintf("%s%s", baseUrl, given.urlPath))
// then
Expect(err).ToNot(HaveOccurred())

// when
received, err := io.ReadAll(resp.Body)
// when
received, err := io.ReadAll(resp.Body)

// then
Expect(resp.Body.Close()).To(Succeed())
Expect(err).ToNot(HaveOccurred())
// then
Expect(resp.Body.Close()).To(Succeed())
Expect(err).ToNot(HaveOccurred())

// then
Expect(err).ToNot(HaveOccurred())
Expect(string(received)).To(ContainSubstring(given.expected))
},
Entry("should not serve index.html without path", testCase{
urlPath: "/gui",
expected: "GUI is disabled. If this is a Zone CP, please check the GUI on the Global CP.",
}),
Entry("should not serve index.html with / path", testCase{
urlPath: "/gui/",
expected: "GUI is disabled. If this is a Zone CP, please check the GUI on the Global CP.",
}),
Entry("should not serve config.json", testCase{
urlPath: "/gui/config.json",
expected: "GUI is disabled. If this is a Zone CP, please check the GUI on the Global CP.",
}),
)
})
Expect(received).To(WithTransform(func(in []byte) []byte {
// Remove the part of the file name that changes always
r := regexp.MustCompile(`index[\-\.][a-z0-9]+\.`).ReplaceAll(in, []byte("index."))
r = regexp.MustCompile(`"version":"[^"]*"`).ReplaceAll(r, []byte(`"version":"0.0.0"`))
if r[len(r)-1] != '\n' {
r = append(r, '\n')
}
return r
}, matchers.MatchGoldenEqual(filepath.Join("testdata", "gui", given.expectedFile))))
},
Entry("should serve index.html without path", testCase{
urlPath: "/gui",
expectedFile: "index.html",
}),
Entry("should serve robots.txt correctly", testCase{
urlPath: "/gui/robots.txt",
expectedFile: "robots.txt",
}),
Entry("should serve on different path", testCase{
urlPath: "/gui/meshes",
expectedFile: "gui_other_files.html",
}),
Entry("should serve index.html with / path", testCase{
urlPath: "/gui/",
expectedFile: "index.html",
}),
Entry("should serve index.html on alternative path", testCase{
urlPath: "/ui/foo",
expectedFile: "gui_with_base_path.html",
basePath: "/ui",
}),
Entry("should serve index.html on alternative path with end /", testCase{
urlPath: "/ui/",
expectedFile: "gui_with_base_path_with_slash.html",
basePath: "/ui/",
}),
Entry("should serve index.html with path from rootUrl", testCase{
urlPath: "/gui/",
expectedFile: "gui_with_root_url.html",
guiRootUrl: "https://foo.com/gui/foo",
}),
Entry("should serve index.html with path from rootUrl even with basePath set", testCase{
urlPath: "/foo/",
expectedFile: "gui_with_root_url_and_base_path.html",
basePath: "/foo",
guiRootUrl: "https://foo.com/gui/foo",
}),
Entry("should serve index.html on alternative path", testCase{
urlPath: "/ui/foo",
expectedFile: "gui_with_base_path.html",
basePath: "/ui",
}),
Entry("should not serve index.html without path", testCase{
urlPath: "/gui",
disabled: true,
expectedFile: "gui_disabled_index.html",
}),
Entry("disabled should serve index.html with / path and disabled:true", testCase{
urlPath: "/gui/",
disabled: true,
expectedFile: "gui_disabled_index_with_slash.html",
}),
Entry("favicon and disabled:true", testCase{
urlPath: "/gui/robots.txt",
disabled: true,
expectedFile: "robots.txt",
}),
Entry("should not serve config.json", testCase{
urlPath: "/gui/config.json",
disabled: true,
expectedFile: "gui_disabled_config.html",
}),
)
})
6 changes: 1 addition & 5 deletions pkg/api-server/index_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
kuma_version "github.com/kumahq/kuma/pkg/version"
)

func addIndexWsEndpoints(ws *restful.WebService, getInstanceId func() string, getClusterId func() string, enableGUI bool, guiURL string) error {
func addIndexWsEndpoints(ws *restful.WebService, getInstanceId func() string, getClusterId func() string, guiURL string) error {
hostname, err := os.Hostname()
if err != nil {
return err
Expand All @@ -32,10 +32,6 @@ func addIndexWsEndpoints(ws *restful.WebService, getInstanceId func() string, ge
clusterId = getClusterId()
}

if !enableGUI {
guiURL = ""
}

response := types.IndexResponse{
Hostname: hostname,
Tagline: kuma_version.Product,
Expand Down
13 changes: 8 additions & 5 deletions pkg/api-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,12 @@ func NewApiServer(
addInspectEndpoints(ws, cfg, meshContextBuilder, resManager)
addInspectEnvoyAdminEndpoints(ws, cfg, resManager, access.EnvoyAdminAccess, envoyAdminClient)
addZoneEndpoints(ws, resManager)
guiUrl := cfg.ApiServer.GUI.BasePath
if cfg.ApiServer.GUI.RootUrl != "" {
guiUrl = cfg.ApiServer.GUI.RootUrl
guiUrl := ""
if cfg.ApiServer.GUI.Enabled && !cfg.IsFederatedZoneCP() {
guiUrl = cfg.ApiServer.GUI.BasePath
if cfg.ApiServer.GUI.RootUrl != "" {
guiUrl = cfg.ApiServer.GUI.RootUrl
}
}
apiUrl := cfg.ApiServer.BasePath
if cfg.ApiServer.RootUrl != "" {
Expand All @@ -161,7 +164,7 @@ func NewApiServer(
if err != nil {
return nil, errors.Wrap(err, "could not create configuration webservice")
}
if err := addIndexWsEndpoints(ws, getInstanceId, getClusterId, cfg.ApiServer.GUI.Enabled, guiUrl); err != nil {
if err := addIndexWsEndpoints(ws, getInstanceId, getClusterId, guiUrl); err != nil {
return nil, errors.Wrap(err, "could not create index webservice")
}
addWhoamiEndpoints(ws)
Expand Down Expand Up @@ -198,7 +201,7 @@ func NewApiServer(
basePath = u.Path
}
basePath = strings.TrimSuffix(basePath, "/")
guiHandler, err := NewGuiHandler(guiPath, cfg.ApiServer.GUI.Enabled, GuiConfig{
guiHandler, err := NewGuiHandler(guiPath, !cfg.ApiServer.GUI.Enabled || cfg.IsFederatedZoneCP(), GuiConfig{
BaseGuiPath: basePath,
ApiUrl: apiUrl,
Version: version.Build.Version,
Expand Down
Loading

0 comments on commit ce45423

Please sign in to comment.