Skip to content

Commit

Permalink
Add format=badge which directly serves badges (#67)
Browse files Browse the repository at this point in the history
* feat: Add format=badge

Adds /kromgo/Verdana.ttf to the container which is the font that
shields.io uses for spacing calculations of the svg

* Update README endpoint examples

* docs: render json schema

---------

Co-authored-by: vaskozl <vaskozl@users.noreply.github.com>
  • Loading branch information
vaskozl and vaskozl authored Aug 16, 2024
1 parent 3ba12e7 commit 2954bcc
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 14 deletions.
10 changes: 9 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ WORKDIR /build
COPY . .
RUN go build -ldflags "-s -w -X main.Version=${VERSION} -X main.Gitsha=${REVISION}" ./cmd/kromgo


FROM alpine as fonts

Check warning on line 10 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-image

The 'as' keyword should match the case of the 'from' keyword

FromAsCasing: 'as' and 'FROM' keywords' casing do not match More info: https://docs.docker.com/go/dockerfile/rule/from-as-casing/

RUN apk add --no-cache msttcorefonts-installer
RUN update-ms-fonts

FROM gcr.io/distroless/static-debian12:nonroot
USER nonroot:nonroot
COPY --from=build --chmod=555 /build/kromgo /kromgo/kromgo
COPY --from=fonts --chmod=555 /usr/share/fonts/truetype/msttcorefonts/Verdana.ttf /kromgo/
EXPOSE 8080/tcp 8888/tcp
WORKDIR /kromgo
LABEL \
org.opencontainers.image.title="kromgo" \
org.opencontainers.image.source="https://github.com/kashalls/kromgo"
ENTRYPOINT ["/kromgo/kromgo"]
ENTRYPOINT ["/kromgo/kromgo"]
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Queries take around 5ms ~ 75ms to complete depending on how many breaks my prome

This format is provided to support Shield.io's [Endpoint Badge](https://shields.io/badges/endpoint-badge) endpoint.

`HTTP GET localhost:8080/query?format=endpoint&metric=node_cpu_usage`
`HTTP GET localhost:8080/node_cpu_usage`

```json
{
Expand All @@ -35,7 +35,7 @@ This format is provided to support Shield.io's [Endpoint Badge](https://shields.

### Raw Response

`HTTP GET localhost:8080/query?metric=node_cpu_usage`
`HTTP GET localhost:8080/node_cpu_usage?format=raw`

```json
[
Expand All @@ -49,6 +49,17 @@ This format is provided to support Shield.io's [Endpoint Badge](https://shields.
]
```

### Badge Response

Like the `endpoint` format but serves an svg badge with `label` and `message`

`HTTP GET localhost:8080/node_cpu_usage?format=badge`

```
content-type: image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" ...
```

### 🤝 Gratitude and Thanks

Thanks to all of the people at the [Home Operations](https://discord.gg/home-operations) Discord community. Be sure to check it out, its a blast!
6 changes: 6 additions & 0 deletions cmd/kromgo/init/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type ServerConfig struct {
type KromgoConfig struct {
Prometheus string `yaml:"prometheus,omitempty" json:"prometheus,omitempty"`
Metrics []Metric `yaml:"metrics" json:"metrics"`
Badge Badge `yaml:"badge,omitempty" json:"badge,omitempty"`
}

type Metric struct {
Expand All @@ -50,6 +51,11 @@ type MetricColor struct {
ValueOverride string `yaml:"valueOverride,omitempty" json:"valueOverride,omitempty"`
}

type Badge struct {
Font string `yaml:"font" json:"font"`
Size int `yaml:"size" json:"size"`
}

var ConfigPath = "/kromgo/config.yaml" // Default config file path
var ProcessedMetrics map[string]Metric

Expand Down
9 changes: 6 additions & 3 deletions cmd/kromgo/init/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ func Init(config configuration.KromgoConfig, serverConfig configuration.ServerCo
}
}

mainRouter.Get("/{metric}", func(w http.ResponseWriter, r *http.Request) {
kromgo.KromgoRequestHandler(w, r, config)
})
kromgoHandler, err := kromgo.NewKromgoHandler(config)
if err != nil {
log.Fatal("Failed to initialize KromgoHandler", zap.Error(err))
}

mainRouter.Get("/{metric}", kromgoHandler.ServeHTTP)

mainServer := createHTTPServer(fmt.Sprintf("%s:%d", serverConfig.ServerHost, serverConfig.ServerPort), mainRouter, serverConfig.ServerReadTimeout, serverConfig.ServerWriteTimeout)
go func() {
Expand Down
19 changes: 19 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@
"$id": "https://github.com/kashalls/kromgo/cmd/kromgo/init/configuration/kromgo-config",
"$ref": "#/$defs/KromgoConfig",
"$defs": {
"Badge": {
"properties": {
"font": {
"type": "string"
},
"size": {
"type": "integer"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"font",
"size"
]
},
"KromgoConfig": {
"properties": {
"prometheus": {
Expand All @@ -13,6 +29,9 @@
"$ref": "#/$defs/Metric"
},
"type": "array"
},
"badge": {
"$ref": "#/$defs/Badge"
}
},
"additionalProperties": false,
Expand Down
5 changes: 4 additions & 1 deletion config.yaml.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/kashalls/kromgo/main/config.schema.json
badge:
font: Verdana.ttf # Relative to /kromgo - Verdana.ttf is available in the container
size: 12
metrics:
- name: node_cpu_usage
query: "round(cluster:node_cpu:ratio_rate5m * 100, 0.1)"
Expand All @@ -23,4 +26,4 @@ metrics:
colors:
- { valueOverride: 'Ok', color: "green", min: 0, max: 0 }
- { valueOverride: 'Not good', color: "orange", min: 1, max: 1 }
- { valueOverride: 'Whoops', color: "red", min: 2, max: 2 }
- { valueOverride: 'Whoops', color: "red", min: 2, max: 2 }
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22.1

require (
github.com/caarlos0/env/v11 v11.2.2
github.com/essentialkaos/go-badge v1.4.1
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/httprate v0.12.1
github.com/invopop/jsonschema v0.12.0
Expand All @@ -20,6 +21,7 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand All @@ -30,6 +32,7 @@ require (
github.com/prometheus/procfs v0.15.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/sys v0.21.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/caarlos0/env/v11 v11.2.0 h1:kvB1ZmwdWgI3JsuuVUE7z4cY/6Ujr03D0w2WkOOH4Xs=
github.com/caarlos0/env/v11 v11.2.0/go.mod h1:LwgkYk1kDvfGpHthrWWLof3Ny7PezzFwS4QrsJdHTMo=
github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg=
github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1nkuKk=
github.com/essentialkaos/check v1.4.0/go.mod h1:LMKPZ2H+9PXe7Y2gEoKyVAwUqXVgx7KtgibfsHJPus0=
github.com/essentialkaos/go-badge v1.4.1 h1:ZtPY1VzukuK51ryYnOrh8vIUBcSdsNJRL8GG+5QxdFg=
github.com/essentialkaos/go-badge v1.4.1/go.mod h1:CwkvOc0vEK7OnTPNsl9Jx2nxzPakzZl9/ak6GvRFyQA=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/httprate v0.12.0 h1:08D/te3pOTJe5+VAZTQrHxwdsH2NyliiUoRD1naKaMg=
github.com/go-chi/httprate v0.12.0/go.mod h1:TUepLXaz/pCjmCtf/obgOQJ2Sz6rC8fSf5cAt5cnTt0=
github.com/go-chi/httprate v0.12.1 h1:55l3IWrPcipqKb72yBzH+grF51z5w+2Bb/Qmu1bos/E=
github.com/go-chi/httprate v0.12.1/go.mod h1:TUepLXaz/pCjmCtf/obgOQJ2Sz6rC8fSf5cAt5cnTt0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down Expand Up @@ -68,6 +70,8 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
Expand Down
90 changes: 87 additions & 3 deletions pkg/kromgo/kromgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"

"github.com/essentialkaos/go-badge"
"github.com/go-chi/chi/v5"
"github.com/kashalls/kromgo/cmd/kromgo/init/configuration"
"github.com/kashalls/kromgo/cmd/kromgo/init/log"
Expand All @@ -14,13 +16,44 @@ import (
"go.uber.org/zap"
)

func KromgoRequestHandler(w http.ResponseWriter, r *http.Request, config configuration.KromgoConfig) {
type KromgoHandler struct {
Config configuration.KromgoConfig
BadgeGenerator *badge.Generator
}

// NewKromgoHandler initializes the handler with the necessary dependencies
func NewKromgoHandler(config configuration.KromgoConfig) (*KromgoHandler, error) {
var badgeGenerator *badge.Generator
if config.Badge.Font != "" {
size := 11
if config.Badge.Size != 0 {
size = config.Badge.Size
}
ptr, err := badge.NewGenerator(config.Badge.Font, size)
badgeGenerator = ptr
if err != nil {
return nil, err
}
}

return &KromgoHandler{
Config: config,
BadgeGenerator: badgeGenerator,
}, nil
}

func (h *KromgoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
requestMetric := chi.URLParam(r, "metric")
if requestMetric == "query" {
requestMetric = r.URL.Query().Get("metric")
}
requestFormat := r.URL.Query().Get("format")

if requestFormat == "badge" && h.BadgeGenerator == nil {
HandleError(w, r, requestMetric, "Format badge is not configured", http.StatusInternalServerError)
return
}

metric, exists := configuration.ProcessedMetrics[requestMetric]

if !exists {
Expand Down Expand Up @@ -80,10 +113,20 @@ func KromgoRequestHandler(w http.ResponseWriter, r *http.Request, config configu
customResponse = colorConfig.ValueOverride
}

message := metric.Prefix + customResponse + metric.Suffix

if requestFormat == "badge" {
hex := colorNameToHex(colorConfig.Color)

w.Header().Set("Content-Type", "image/svg+xml")
w.Write(h.BadgeGenerator.GenerateFlat(metric.Name, message, hex))
return
}

data := map[string]interface{}{
"schemaVersion": 1,
"label": metric.Name,
"message": metric.Prefix + customResponse + metric.Suffix,
"message": message,
}

if colorConfig.Color != "" {
Expand All @@ -106,4 +149,45 @@ func requestLog(r *http.Request) *zap.Logger {
requestFormat := r.URL.Query().Get("format")

return log.With(zap.String("req_method", r.Method), zap.String("req_path", r.URL.Path), zap.String("metric", requestMetric), zap.String("format", requestFormat))
}
}

func colorNameToHex(colorName string) (string) {
if strings.HasPrefix(colorName, "#") {
return colorName
}

switch colorName {
case "":
return badge.COLOR_BLUE
case "blue":
return badge.COLOR_BLUE
case "brightgreen":
return badge.COLOR_BRIGHTGREEN
case "green":
return badge.COLOR_GREEN
case "grey":
return badge.COLOR_GREY
case "lightgrey":
return badge.COLOR_LIGHTGREY
case "orange":
return badge.COLOR_ORANGE
case "red":
return badge.COLOR_RED
case "yellow":
return badge.COLOR_YELLOW
case "yellowgreen":
return badge.COLOR_YELLOWGREEN
case "success":
return badge.COLOR_SUCCESS
case "important":
return badge.COLOR_IMPORTANT
case "critical":
return badge.COLOR_CRITICAL
case "informational":
return badge.COLOR_INFORMATIONAL
case "inactive":
return badge.COLOR_INACTIVE
default:
return badge.COLOR_GREEN
}
}

0 comments on commit 2954bcc

Please sign in to comment.