Skip to content

Commit

Permalink
Fix the Route Prefix bug and add ExternalURL Support (#190) (#239)
Browse files Browse the repository at this point in the history
Addresses route-prefix bug described in #190

Pass in the prefix to the Static handler such that it can be removed
from the Path looked up to serve files relative to the FileSystem's
root.

* Provide support for --web.external-url

This works in conjunction with the `--web.route-prefix` flag to
provide resources at the provided external URL, with the properly
munged `route-prefix`. If an external URL is provided, but a
`route-prefix` is not, this change takes the external URL's Path
and uses that as the `route-prefix`.

Includes a simple test checking for the presence of a known
external-url in the body of the Status handler.

Signed-off-by: Andrew G10z <[email protected]>
  • Loading branch information
apghero authored and beorn7 committed Apr 2, 2019
1 parent bddec46 commit 6b81e91
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 38 deletions.
42 changes: 21 additions & 21 deletions asset/assets_vfsdata.go

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions handler/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,17 @@ func Ready(ms storage.MetricStore) http.Handler {
// Static serves the static files from the provided http.FileSystem.
//
// The returned handler is already instrumented for Prometheus.
func Static(root http.FileSystem) http.Handler {
func Static(root http.FileSystem, prefix string) http.Handler {
if prefix == "/" {
prefix = ""
}

handler := http.FileServer(root)
return promhttp.InstrumentHandlerCounter(
httpCnt.MustCurryWith(prometheus.Labels{"handler": "static"}),
http.FileServer(root),
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = r.URL.Path[len(prefix):]
handler.ServeHTTP(w, r)
}),
)
}
75 changes: 75 additions & 0 deletions handler/misc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package handler

import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"testing"
)

type fakeFileSystem struct {
files map[string]struct{}
}

// Open implements the http.FileSystem interface
//
// If a file is present, no error will be returned.
// This implementation always returns a nil File.
func (f *fakeFileSystem) Open(name string) (http.File, error) {
log.Println("requesting" + name)

if _, ok := f.files[name]; !ok {
return nil, os.ErrNotExist
}
return os.Open("misc_test.go")
// return nil, nil
}

func TestRoutePrefixForStatic(t *testing.T) {
fs := &fakeFileSystem{map[string]struct{}{
"/index.js": struct{}{},
}}

for _, test := range []struct {
prefix string
path string
code int
}{
{"/", "/index.js", 200},
{"/", "/missing.js", 404},
{"/route-prefix", "/index.js", 200},
{"/route-prefix", "/missing.js", 404},
} {
test := test
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
t.Parallel()
req, err := http.NewRequest(
http.MethodGet, "http://example.com"+test.prefix+test.path, nil,
)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
static := Static(fs, test.prefix)
static.ServeHTTP(w, req)
if test.code != w.Code {
t.Errorf("Wanted %d, got %d.", test.code, w.Code)
}
})
}
}
8 changes: 8 additions & 0 deletions handler/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"html/template"
"io/ioutil"
"net/http"
"path"
"strconv"
"time"

Expand All @@ -36,6 +37,7 @@ type data struct {
Flags map[string]string
BuildInfo map[string]string
Birth time.Time
BaseURL string
counter int
}

Expand Down Expand Up @@ -66,6 +68,11 @@ func Status(
return strconv.FormatFloat(f, 'f', -1, 64)
},
})

externalURL := flags["web.external-url"]
routePrefix := flags["web.route-prefix"]
baseURL := path.Join(externalURL, routePrefix)

f, err := root.Open("template.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Expand Down Expand Up @@ -99,6 +106,7 @@ func Status(
MetricGroups: ms.GetMetricFamiliesMap(),
BuildInfo: buildInfo,
Birth: birth,
BaseURL: baseURL,
}
d.Flags = map[string]string{}
// Exclude kingpin default flags to expose only Prometheus ones.
Expand Down
52 changes: 52 additions & 0 deletions handler/status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package handler

import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/prometheus/pushgateway/asset"
"github.com/prometheus/pushgateway/storage"
)

func TestExternalURLPresenceInPage(t *testing.T) {
flags := map[string]string{
"web.listen-address": ":9091",
"web.telemetry-path": "/metrics",
"web.external-url": "http://web-external-url.com",
}

ms := storage.NewDiskMetricStore("", time.Minute, nil)
status := Status(ms, asset.Assets, flags)
defer ms.Shutdown()

w := httptest.NewRecorder()
status.ServeHTTP(w, &http.Request{})

if http.StatusOK != w.Code {
t.Fatalf("Wanted status %d, got %d", http.StatusOK, w.Code)
}

var rawBody []byte
w.Result().Body.Read(rawBody)
body := string(rawBody)

if index := strings.Index(body, flags["web.external-url"]); index > 0 {
t.Errorf("Wanted index of %q > 0 , got %d", flags["web.external-url"], index)
}
}
37 changes: 29 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"net"
"net/http"
"net/http/pprof"
"net/url"
"os"
"os/signal"
"path"
Expand Down Expand Up @@ -49,7 +50,8 @@ func main() {

listenAddress = app.Flag("web.listen-address", "Address to listen on for the web interface, API, and telemetry.").Default(":9091").String()
metricsPath = app.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String()
routePrefix = app.Flag("web.route-prefix", "Prefix for the internal routes of web endpoints.").Default("").String()
externalURL = app.Flag("web.external-url", "The URL under which the Pushgateway is externally reachable.").Default("").URL()
routePrefix = app.Flag("web.route-prefix", "Prefix for the internal routes of web endpoints. Defaults to the path of --web.external-url.").Default("").String()
persistenceFile = app.Flag("persistence.file", "File to persist metrics. If empty, metrics are only kept in memory.").Default("").String()
persistenceInterval = app.Flag("persistence.interval", "The minimum interval at which to write out the persistence file.").Default("5m").Duration()
)
Expand All @@ -58,16 +60,14 @@ func main() {
app.HelpFlag.Short('h')
kingpin.MustParse(app.Parse(os.Args[1:]))

if *routePrefix == "/" {
*routePrefix = ""
}
if *routePrefix != "" {
*routePrefix = "/" + strings.Trim(*routePrefix, "/")
}
*routePrefix = computeRoutePrefix(*routePrefix, *externalURL)

log.Infoln("Starting pushgateway", version.Info())
log.Infoln("Build context", version.BuildContext())
log.Debugf("Prefix path is '%s'", *routePrefix)
log.Debugf("External URL is '%s'", *externalURL)

(*externalURL).Path = ""

flags := map[string]string{}
for _, f := range app.Model().Flags {
Expand Down Expand Up @@ -96,7 +96,7 @@ func main() {
r.POST(pushAPIPath+"/job/:job", handler.Push(ms, false))
r.DELETE(pushAPIPath+"/job/:job", handler.Delete(ms))

r.Handler("GET", *routePrefix+"/static/*filepath", handler.Static(asset.Assets))
r.Handler("GET", *routePrefix+"/static/*filepath", handler.Static(asset.Assets, *routePrefix))

statusHandler := handler.Status(ms, asset.Assets, flags)
r.Handler("GET", *routePrefix+"/status", statusHandler)
Expand Down Expand Up @@ -135,6 +135,27 @@ func handlePprof(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
}
}

// computeRoutePrefix returns the effective route prefix based on the
// provided flag values for --web.route-prefix and
// --web.external-url. With prefix empty, the path of externalURL is
// used instead. A prefix "/" results in an empty returned prefix. Any
// non-empty prefix is normalized to start, but not to end, with "/".
func computeRoutePrefix(prefix string, externalURL *url.URL) string {
if prefix == "" {
prefix = externalURL.Path
}

if prefix == "/" {
prefix = ""
}

if prefix != "" {
prefix = "/" + strings.Trim(prefix, "/")
}

return prefix
}

func interruptHandler(l net.Listener) {
notifier := make(chan os.Signal, 1)
signal.Notify(notifier, os.Interrupt, syscall.SIGTERM)
Expand Down
13 changes: 6 additions & 7 deletions resources/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Prometheus Pushgateway</title>

{{- $prefix := index .Flags "web.route-prefix" }}
<script src="{{$prefix}}/static/jquery-2.1.4.min.js"></script>
<link rel="stylesheet" href="{{$prefix}}/static/bootstrap-3.3.4-dist/css/bootstrap.min.css">
<link rel="stylesheet" href="{{$prefix}}/static/bootstrap-3.3.4-dist/css/bootstrap-theme.min.css">
<script src="{{$prefix}}/static/bootstrap-3.3.4-dist/js/bootstrap.min.js"></script>
<script src="{{$prefix}}/static/functions.js"></script>
<script src="{{.BaseURL}}/static/jquery-2.1.4.min.js"></script>
<link rel="stylesheet" href="{{.BaseURL}}/static/bootstrap-3.3.4-dist/css/bootstrap.min.css">
<link rel="stylesheet" href="{{.BaseURL}}/static/bootstrap-3.3.4-dist/css/bootstrap-theme.min.css">
<script src="{{.BaseURL}}/static/bootstrap-3.3.4-dist/js/bootstrap.min.js"></script>
<script src="{{.BaseURL}}/static/functions.js"></script>

<style type="text/css">
.cursor-pointer {
Expand Down Expand Up @@ -244,6 +243,6 @@ <h3 id="del-header">Deletion Confirmation</h3>
</div>
</div>
</div>

</body>
</html>

0 comments on commit 6b81e91

Please sign in to comment.