Skip to content
Permalink
Browse files

feat(token): support custom token from prometheus

  • Loading branch information...
appleboy committed Aug 19, 2019
1 parent 7b4e7ee commit cf3084ca200c8fa9471e9031b426f18b08636834
Showing with 84 additions and 16 deletions.
  1. +14 −11 README.md
  2. +38 −5 prom.go
  3. +32 −0 prom_test.go
@@ -1,4 +1,5 @@
# ginprom

Gin Prometheus metrics exporter inspired by [github.com/zsais/go-gin-prometheus](https://github.com/zsais/go-gin-prometheus)

![Go Version](https://img.shields.io/badge/go-1.9-brightgreen.svg)
@@ -8,7 +9,6 @@ Gin Prometheus metrics exporter inspired by [github.com/zsais/go-gin-prometheus]
[![codecov](https://codecov.io/gh/Depado/ginprom/branch/master/graph/badge.svg)](https://codecov.io/gh/Depado/ginprom)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Depado/bfchroma/blob/master/LICENSE)


## Install

Simply run :
@@ -35,8 +35,8 @@ func main() {
r := gin.Default()
p := ginprom.New(
ginprom.Engine(r),
ginprom.Subsystem("gin"),
ginprom.Path("/metrics"),
ginprom.Subsystem("gin"),
ginprom.Path("/metrics"),
)
r.Use(p.Instrument())
@@ -61,30 +61,33 @@ Specify the subsystem
Default : "go"

`Engine(e *gin.Engine)`
Specify the Gin engine directly when initializing.
Saves a call to `Use(e *gin.Engine)`
Specify the Gin engine directly when initializing.
Saves a call to `Use(e *gin.Engine)`
Default : `nil`

`Ignore(paths ...string)`
`Ignore(paths ...string)`
Specify which paths should not be taken into account by the middleware.

`Token(token string)`
Specify the token from prometheus. It is returned when the api request token is invalid.

## Troubleshooting

### The instrumentation doesn't seem to work

Make sure you have set the `gin.Engine` in the `ginprom` middleware, either when
initializing it using `ginprom.New(ginprom.Engine(r))` or using the `Use`
initializing it using `ginprom.New(ginprom.Engine(r))` or using the `Use`
function after the initialization like this :

```go
p := ginprom.New(
ginprom.Namespace("gin"),
ginprom.Subsystem("gonic"),
ginprom.Path("/metrics"),
ginprom.Namespace("gin"),
ginprom.Subsystem("gonic"),
ginprom.Path("/metrics"),
)
p.Use(r)
r.Use(p.Instrument())
```

By design, if the middleware was to panic, it would do so when a route is
called. That's why it just silently fails when no engine has been set.
called. That's why it just silently fails when no engine has been set.
43 prom.go
@@ -1,6 +1,9 @@
package ginprom

import (
"errors"
"fmt"
"net/http"
"strconv"
"sync"
"time"
@@ -11,8 +14,9 @@ import (
)

var defaultPath = "/metrics"
var defaultNs = "gin"
var defaultSys = "gonic"
var defaultNs = "gin"
var defaultSys = "gonic"
var errInvalidToken = errors.New("Invalid or missing token")

type pmap struct {
sync.RWMutex
@@ -32,6 +36,7 @@ type Prometheus struct {
MetricsPath string
Namespace string
Subsystem string
Token string
Ignored pmapb
Engine *gin.Engine
PathMap pmap
@@ -74,6 +79,15 @@ func Namespace(ns string) func(*Prometheus) {
}
}

// Token is an option allowing to set the bearer token in prometheus
// with New.
// Example : ginprom.New(ginprom.Token("your_custom_token"))
func Token(token string) func(*Prometheus) {
return func(p *Prometheus) {
p.Token = token
}
}

// Engine is an option allowing to set the gin engine when intializing with New.
// Example :
// r := gin.Default()
@@ -100,7 +114,7 @@ func New(options ...func(*Prometheus)) *Prometheus {
}
p.register()
if p.Engine != nil {
p.Engine.GET(p.MetricsPath, prometheusHandler())
p.Engine.GET(p.MetricsPath, prometheusHandler(p.Token))
}

return p
@@ -214,13 +228,32 @@ func (p *Prometheus) Instrument() gin.HandlerFunc {
// Use is a method that should be used if the engine is set after middleware
// initialization
func (p *Prometheus) Use(e *gin.Engine) {
e.GET(p.MetricsPath, prometheusHandler())
e.GET(p.MetricsPath, prometheusHandler(p.Token))
p.Engine = e
}

func prometheusHandler() gin.HandlerFunc {
func prometheusHandler(token string) gin.HandlerFunc {
h := promhttp.Handler()
return func(c *gin.Context) {
if token == "" {
h.ServeHTTP(c.Writer, c.Request)
return
}

header := c.Request.Header.Get("Authorization")

if header == "" {
c.String(http.StatusUnauthorized, errInvalidToken.Error())
return
}

bearer := fmt.Sprintf("Bearer %s", token)

if header != bearer {
c.String(http.StatusUnauthorized, errInvalidToken.Error())
return
}

h.ServeHTTP(c.Writer, c.Request)
}
}
@@ -49,6 +49,15 @@ func TestPath(t *testing.T) {
}
}

func TestToken(t *testing.T) {
valid := []string{"token1", "token2", ""}
for _, tt := range valid {
p := New(Token(tt))
assert.Equal(t, tt, p.Token)
unregister(p)
}
}

func TestEngine(t *testing.T) {
r := gin.New()
p := New(Engine(r))
@@ -224,3 +233,26 @@ func TestMetricsPathIgnored(t *testing.T) {
})
unregister(p)
}

func TestMetricsBearerToken(t *testing.T) {
r := gin.New()
p := New(Engine(r), Token("test-1234"))
r.Use(p.Instrument())

g := gofight.New()

g.GET(p.MetricsPath).Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Equal(t, http.StatusUnauthorized, r.Code)
assert.Equal(t, errInvalidToken.Error(), r.Body.String())
})

g.GET(p.MetricsPath).
SetHeader(gofight.H{
"Authorization": "Bearer " + "test-1234",
}).
Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Equal(t, http.StatusOK, r.Code)
assert.NotContains(t, r.Body.String(), fmt.Sprintf("%s_requests_total", p.Subsystem))
})
unregister(p)
}

0 comments on commit cf3084c

Please sign in to comment.
You can’t perform that action at this time.