Merge pull request #5 from cloudflare/bin-assets

Binary assets and other improvements
This commit is contained in:
Łukasz Mierzwa
2017-03-26 17:52:27 -07:00
committed by GitHub
81 changed files with 1922 additions and 134 deletions

66
.gitmodules vendored
View File

@@ -13,54 +13,54 @@
[submodule "vendor/github.com/gin-gonic/gin"]
path = vendor/github.com/gin-gonic/gin
url = https://github.com/gin-gonic/gin
[submodule "vendor/github.com/golang/protobuf"]
path = vendor/github.com/golang/protobuf
url = https://github.com/golang/protobuf
[submodule "vendor/github.com/hansrodtang/randomcolor"]
path = vendor/github.com/hansrodtang/randomcolor
url = https://github.com/hansrodtang/randomcolor
[submodule "vendor/github.com/kelseyhightower/envconfig"]
path = vendor/github.com/kelseyhightower/envconfig
url = https://github.com/kelseyhightower/envconfig
[submodule "vendor/github.com/manucorporat/sse"]
path = vendor/github.com/manucorporat/sse
url = https://github.com/manucorporat/sse
[submodule "vendor/github.com/mattn/go-isatty"]
path = vendor/github.com/mattn/go-isatty
url = https://github.com/mattn/go-isatty
[submodule "vendor/github.com/patrickmn/go-cache"]
path = vendor/github.com/patrickmn/go-cache
url = https://github.com/patrickmn/go-cache
[submodule "vendor/golang.org/x/net"]
path = vendor/golang.org/x/net
url = https://go.googlesource.com/net
[submodule "vendor/gopkg.in/go-playground/validator.v8"]
path = vendor/gopkg.in/go-playground/validator.v8
url = https://gopkg.in/go-playground/validator.v8
[submodule "vendor/gopkg.in/yaml.v2"]
path = vendor/gopkg.in/yaml.v2
url = https://gopkg.in/yaml.v2
[submodule "vendor/github.com/certifi/gocertifi"]
path = vendor/github.com/certifi/gocertifi
url = https://github.com/certifi/gocertifi
[submodule "vendor/github.com/getsentry/raven-go"]
path = vendor/github.com/getsentry/raven-go
url = https://github.com/getsentry/raven-go
[submodule "vendor/github.com/gin-gonic/contrib"]
path = vendor/github.com/gin-gonic/contrib
url = https://github.com/gin-gonic/contrib
[submodule "vendor/github.com/beorn7/perks"]
path = vendor/github.com/beorn7/perks
url = https://github.com/beorn7/perks
[submodule "vendor/github.com/matttproud/golang_protobuf_extensions"]
path = vendor/github.com/matttproud/golang_protobuf_extensions
url = https://github.com/matttproud/golang_protobuf_extensions
[submodule "vendor/github.com/mcuadros/go-gin-prometheus"]
path = vendor/github.com/mcuadros/go-gin-prometheus
url = https://github.com/mcuadros/go-gin-prometheus
[submodule "vendor/github.com/prometheus/client_golang"]
path = vendor/github.com/prometheus/client_golang
url = https://github.com/prometheus/client_golang
[submodule "vendor/gopkg.in/jarcoal/httpmock.v1"]
path = vendor/gopkg.in/jarcoal/httpmock.v1
url = https://gopkg.in/jarcoal/httpmock.v1
[submodule "vendor/github.com/elazarl/go-bindata-assetfs"]
path = vendor/github.com/elazarl/go-bindata-assetfs
url = https://github.com/elazarl/go-bindata-assetfs
[submodule "vendor/github.com/gin-contrib/static"]
path = vendor/github.com/gin-contrib/static
url = https://github.com/gin-contrib/static
[submodule "vendor/github.com/beorn7/perks"]
path = vendor/github.com/beorn7/perks
url = https://github.com/beorn7/perks
[submodule "vendor/github.com/certifi/gocertifi"]
path = vendor/github.com/certifi/gocertifi
url = https://github.com/certifi/gocertifi
[submodule "vendor/github.com/golang/protobuf"]
path = vendor/github.com/golang/protobuf
url = https://github.com/golang/protobuf
[submodule "vendor/github.com/manucorporat/sse"]
path = vendor/github.com/manucorporat/sse
url = https://github.com/manucorporat/sse
[submodule "vendor/github.com/mattn/go-isatty"]
path = vendor/github.com/mattn/go-isatty
url = https://github.com/mattn/go-isatty
[submodule "vendor/github.com/matttproud/golang_protobuf_extensions"]
path = vendor/github.com/matttproud/golang_protobuf_extensions
url = https://github.com/matttproud/golang_protobuf_extensions
[submodule "vendor/github.com/prometheus/client_model"]
path = vendor/github.com/prometheus/client_model
url = https://github.com/prometheus/client_model
@@ -70,6 +70,12 @@
[submodule "vendor/github.com/prometheus/procfs"]
path = vendor/github.com/prometheus/procfs
url = https://github.com/prometheus/procfs
[submodule "vendor/gopkg.in/jarcoal/httpmock.v1"]
path = vendor/gopkg.in/jarcoal/httpmock.v1
url = https://gopkg.in/jarcoal/httpmock.v1
[submodule "vendor/golang.org/x/net"]
path = vendor/golang.org/x/net
url = https://go.googlesource.com/net
[submodule "vendor/gopkg.in/go-playground/validator.v8"]
path = vendor/gopkg.in/go-playground/validator.v8
url = https://gopkg.in/go-playground/validator.v8
[submodule "vendor/gopkg.in/yaml.v2"]
path = vendor/gopkg.in/yaml.v2
url = https://gopkg.in/yaml.v2

View File

@@ -1,4 +1,4 @@
FROM golang:1.7.5-alpine3.5
FROM golang:alpine3.5
ADD . /go/src/github.com/cloudflare/unsee
@@ -6,11 +6,7 @@ ARG VERSION
RUN go install \
-ldflags "-X main.version=${VERSION:-dev}" \
github.com/cloudflare/unsee
RUN mv /go/src/github.com/cloudflare/unsee/static \
/go/src/github.com/cloudflare/unsee/templates \
/go/ && \
rm -fr /go/src
github.com/cloudflare/unsee && \
rm -fr /go/src
CMD ["unsee"]

View File

@@ -1,44 +1,69 @@
NAME := unsee
VERSION := $(shell git describe --tags --always --dirty='-dev')
# Alertmanager instance used when running locally, points to mock data
ALERTMANAGER_URI := https://raw.githubusercontent.com/prymitive/alertmanager-demo-api/master
# Listen port when running locally
PORT := 8080
SOURCES := $(wildcard *.go) $(wildcard */*.go)
ASSET_SOURCES := $(wildcard assets/*/* assets/*/*/*)
GO_BINDATA_MODE := prod
ifdef DEBUG
GO_BINDATA_FLAGS = -debug
GO_BINDATA_MODE = debug
endif
.DEFAULT_GOAL := $(NAME)
.build/deps.ok: .gitmodules
git submodule update --init --recursive
go get -u github.com/jteeuwen/go-bindata/...
go get -u github.com/elazarl/go-bindata-assetfs/...
mkdir -p .build
touch $@
.build/bindata_assetfs.%:
mkdir -p .build
rm -f .build/bindata_assetfs.*
touch $@
bindata_assetfs.go: .build/deps.ok .build/bindata_assetfs.$(GO_BINDATA_MODE) $(ASSET_SOURCES)
go-bindata-assetfs $(GO_BINDATA_FLAGS) -prefix assets -nometadata assets/templates/... assets/static/...
$(NAME): .build/deps.ok bindata_assetfs.go $(SOURCES)
go build -ldflags "-X main.version=$(VERSION)"
.PHONY: run
run: $(NAME)
DEBUG=true ALERTMANAGER_URI=$(ALERTMANAGER_URI) PORT=$(PORT) ./unsee
.PHONY: docker-image
docker-image:
docker build --build-arg VERSION=$(VERSION) -t unsee:$(VERSION) .
docker-image: bindata_assetfs.go
docker build --build-arg VERSION=$(VERSION) -t $(NAME):$(VERSION) .
ALERTMANAGER_URI := https://raw.githubusercontent.com/prymitive/alertmanager-demo-api/master
PORT := 8080
.PHONY: demo
demo: docker-image
@docker rm -f unsee-dev || true
.PHONY: run-docker
run-docker: docker-image
@docker rm -f $(NAME) || true
docker run \
--name unsee-dev \
--name $(NAME) \
-e ALERTMANAGER_URI=$(ALERTMANAGER_URI) \
-e PORT=$(PORT) \
-p $(PORT):$(PORT) \
unsee:$(VERSION)
.PHONY: dev
dev: .build/deps.ok
go build -v -ldflags "-X main.version=${VERSION:-dev}" && \
DEBUG=true \
ALERTMANAGER_URI=$(ALERTMANAGER_URI) \
PORT=$(PORT) \
./unsee
$(NAME):$(VERSION)
.PHONY: lint
lint: .build/deps.ok
@golint ./... | (grep -v ^vendor/ || true)
@golint ./... | (egrep -v "^vendor/|^bindata_assetfs.go" || true)
.PHONY: test
test: lint
test: lint bindata_assetfs.go
@go test -cover `go list ./... | grep -v /vendor/`
ASSETS_DIR := $(CURDIR)/static/assets
#======================== asset helper targets =================================
ASSETS_DIR := $(CURDIR)/assets/static/managed
CDNJS_PREFIX := https://cdnjs.cloudflare.com/ajax/libs
%.js:

View File

@@ -9,15 +9,35 @@ to alert data, therefore safe to be accessed by wider audience.
## Building and running
### Running in dev mode
### Building from source
Requires Go.
To clone git repo and build the binary yourself run:
make dev
git clone https://github.com/cloudflare/unsee $GOPATH/src/github.com/cloudflare/unsee
cd $GOPATH/src/github.com/cloudflare/unsee
make
Will compile unsee and run the binary (not using Docker), by default will use
same port and Alertmanager URI as demo mode. This is intended for local
development.
`unsee` binary will be compiled in project directory.
### Running
`unsee` is configured via environment variables or command line flags.
Environment variable `ALERTMANAGER_URI` or cli flag `-alertmanager.uri` is the
only option required to run. See `Environment variables` section below for the
full list of supported environment variables. Examples:
ALERTMANAGER_URI=https://alertmanager.example.com unsee
unsee -alertmanager.uri https://alertmanager.example.com
There is a make target which will compile and run unsee:
make run
By default it will listen on port `8080` and Alertmanager mock data will be
used, to override Alertmanager URI set `ALERTMANAGER_URI` and/or `PORT` make
variables. Example:
make PORT=5000 ALERTMANAGER_URI=https://alertmanager.example.com run
### Build a Docker image
@@ -25,17 +45,14 @@ development.
This will build a Docker image from sources.
### Running the Docker image in demo mode
### Running the Docker image
make demo
make run-docker
Will run locally build Docker image. This is intended for testing build Docker
images or checking unsee functionality.
By default unsee will listen on port `8080` and Alertmanager mock data will be
used, to override Alertmanager URI set `ALERTMANAGER_URI` and/or `PORT` make
variables. Example:
Will run locally built Docker image. Same defaults and override variables
apply as with `make run`. Example:
make PORT=5000 ALERTMANAGER_URI=https://alertmanager.example.com run
make PORT=5000 ALERTMANAGER_URI=https://alertmanager.example.com run-docker
### Environment variables
@@ -172,6 +189,18 @@ This option can also be set using `-jira.regex` flag. Example:
This variable is optional and default is not set (no rule will be applied).
#### PORT
HTTP port to listen on. Example:
PORT=8000
This option can also be set using `-port` flag. Example:
$ unsee -port 8000
Default is `8080`.
#### SENTRY_DSN
DSN for [Sentry](https://sentry.io) integration in Go. See

View File

@@ -1,35 +1,97 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"strings"
log "github.com/Sirupsen/logrus"
assetfs "github.com/elazarl/go-bindata-assetfs"
"html/template"
"net/http"
)
// ReadAssets will read assets.txt file in given directory and return a list
type binaryFileSystem struct {
fs http.FileSystem
}
func (b *binaryFileSystem) Open(name string) (http.File, error) {
return b.fs.Open(name)
}
func (b *binaryFileSystem) Exists(prefix string, filepath string) bool {
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
if _, err := b.fs.Open(p); err != nil {
return false
}
return true
}
return false
}
func newBinaryFileSystem(root string) *binaryFileSystem {
fs := &assetfs.AssetFS{
Asset: Asset,
// Don't render directory index, return 404 for /static/ requests)
AssetDir: func(path string) ([]string, error) { return nil, errors.New("Not found") },
Prefix: root,
}
return &binaryFileSystem{
fs,
}
}
// readAssets will read assets.txt file in given directory and return a list
// of file names in that file
// assets.txt contains a list of external js of css files that are mirrored
// in static/assets directory that should be loaded in the browser
// this way we don't have to maintain this list in the Makefile that does
// the mirroring and in the template
func ReadAssets(kind string) []string {
filename := fmt.Sprintf("./static/assets/%s/assets.txt", kind)
content, err := ioutil.ReadFile(filename)
func readAssets(kind string) []string {
indexPath := fmt.Sprintf("static/managed/%s/assets.txt", kind)
assetIndex, err := Asset(indexPath)
if err != nil {
log.Error(err.Error())
log.Error(err)
return []string{}
}
lines := strings.Split(string(content), "\n")
ret := []string{}
for _, l := range lines {
for _, l := range strings.Split(string(assetIndex), "\n") {
if l != "" {
ret = append(ret, l)
}
}
return ret
}
// load all templates from binary asset resource
// this function will iterate all files with given prefix (e.g. /templates/)
// and return Template instance with all templates loaded
func loadTemplates(prefix string) *template.Template {
var t *template.Template
for _, filename := range AssetNames() {
if strings.HasPrefix(filename, prefix) {
templateContent, err := Asset(filename)
if err != nil {
log.Fatal(err)
}
var tmpl *template.Template
if t == nil {
// if template wasn't yet initialized do it here
t = template.New(filename)
}
if filename == t.Name() {
tmpl = t
} else {
// if we already have an instance of template.Template then
// add a new file to it
tmpl = t.New(filename)
}
_, err = tmpl.Parse(string(templateContent))
if err != nil {
log.Fatal(err)
return nil
}
}
}
return t
}

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 434 KiB

View File

@@ -1,4 +1,4 @@
<script type="application/javascript" id="reload-needed">
<script type="application/json" id="reload-needed">
%div.jumbotron
%h1.text-center
New version detected, reloading ...
@@ -6,7 +6,7 @@
</script>
<script type="application/javascript" id="update-error">
<script type="application/json" id="update-error">
%div.jumbotron
%h1.text-center
=error
@@ -23,7 +23,7 @@
</script>
<script type="application/javascript" id="fatal-error">
<script type="application/json" id="fatal-error">
%div.jumbotron
%h1.text-center
Critical error
@@ -40,7 +40,7 @@
</script>
<script type="application/javascript" id="internal-error">
<script type="application/json" id="internal-error">
%div.jumbotron
%h1.text-center
Internal error

View File

@@ -1,4 +1,4 @@
<script type="application/javascript" id="groups">
<script type="application/json" id="groups">
%div.incident{id: group.id, 'data-hash': group.hash}
-var cls_panel = 'panel-success';
-if (group.unsilencedCount > 0) {

View File

@@ -13,7 +13,7 @@
<title>(◕︵◕)</title>
{{ range .CSSFiles }}
<link rel="stylesheet" href="/static/assets/css/{{ . }}"> {{- end }}
<link rel="stylesheet" href="/static/managed/css/{{ . }}"> {{- end }}
<link rel="stylesheet" href="/static/base.css">

View File

@@ -13,7 +13,7 @@
<title>(◕︵◕)</title>
{{ range .CSSFiles }}
<link rel="stylesheet" href="/static/assets/css/{{ . }}">
<link rel="stylesheet" href="/static/managed/css/{{ . }}">
{{- end }}
<link rel="stylesheet" href="/static/base.css?_={{ .NowQ }}">
@@ -144,10 +144,10 @@
</div>
{{ range .JSFiles }}
<script type="text/javascript" src="/static/assets/js/{{ . }}"></script>
<script type="text/javascript" src="/static/managed/js/{{ . }}"></script>
{{- end }}
{{ template "js.html" .}}
{{ template "templates/js.html" .}}
<div class="modal fade" id="labelModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
@@ -163,7 +163,7 @@
</body>
</html>
{{ template "groups.haml"}}
{{ template "summary.haml" }}
{{ template "errors.haml" }}
{{ template "modal.haml"}}
{{ template "templates/groups.haml" }}
{{ template "templates/summary.haml" }}
{{ template "templates/errors.haml" }}
{{ template "templates/modal.haml" }}

View File

@@ -1,4 +1,4 @@
<script type="application/javascript" id="modal-title">
<script type="application/json" id="modal-title">
%button.close{type: "button", "data-dismiss": "modal"}
%i.fa.fa-close
%div.label-list.label{class: attrs.class, style: attrs.style}
@@ -7,7 +7,7 @@
=counter
</script>
<script type="application/javascript" id="modal-body">
<script type="application/json" id="modal-body">
%table.table.table-striped
%caption.text-center
Quick filters

View File

@@ -1,11 +1,11 @@
<script type="application/javascript" id="breakdown">
<script type="application/json" id="breakdown">
%div.popover
%div.arrow
%h1.popover-title.text-center
%div.popover-content{id: 'breakdown-content'}
</script>
<script type="application/javascript" id="breakdown-content">
<script type="application/json" id="breakdown-content">
-if (tags.length > 0) {
%table.table
-$.each(tags, function(i, tag) {

1661
bindata_assetfs.go Normal file

File diff suppressed because one or more lines are too long

View File

@@ -30,6 +30,7 @@ type configEnvs struct {
Debug bool `envconfig:"DEBUG" default:"false" help:"Enable debug mode"`
FilterDefault string `envconfig:"FILTER_DEFAULT" help:"Default filter string"`
JiraRegexp spaceSeparatedList `envconfig:"JIRA_REGEX" help:"List of JIRA regex rules"`
Port int `envconfig:"PORT" default:"8080" help:"HTTP port to listen on"`
SentryDSN string `envconfig:"SENTRY_DSN" help:"Sentry DSN for Go exceptions"`
SentryPublicDSN string `envconfig:"SENTRY_PUBLIC_DSN" help:"Sentry DSN for javascript exceptions"`
StripLabels spaceSeparatedList `envconfig:"STRIP_LABELS" help:"List of labels to ignore"`

24
main.go
View File

@@ -10,6 +10,7 @@ import (
"github.com/DeanThompson/ginpprof"
log "github.com/Sirupsen/logrus"
raven "github.com/getsentry/raven-go"
"github.com/gin-contrib/static"
"github.com/gin-gonic/contrib/sentry"
"github.com/gin-gonic/gin"
ginprometheus "github.com/mcuadros/go-gin-prometheus"
@@ -68,6 +69,18 @@ func init() {
metricAlertmanagerErrors.With(prometheus.Labels{"endpoint": "silences"}).Set(0)
}
func setupRouter(router *gin.Engine) {
router.SetHTMLTemplate(loadTemplates("templates"))
router.Use(static.Serve("/static", newBinaryFileSystem("static")))
router.GET("/favicon.ico", favicon)
router.GET("/", index)
router.GET("/help", help)
router.GET("/alerts.json", alerts)
router.GET("/autocomplete.json", autocomplete)
}
func main() {
log.Infof("Version: %s", version)
@@ -93,6 +106,7 @@ func main() {
}
router := gin.New()
setupRouter(router)
prom := ginprometheus.NewPrometheus("gin")
prom.Use(router)
@@ -106,15 +120,5 @@ func main() {
router.Use(sentry.Recovery(raven.DefaultClient, false))
}
router.LoadHTMLGlob("templates/*")
router.Static("/static", "./static")
router.StaticFile("/favicon.ico", "./static/favicon.ico")
router.GET("/", Index)
router.GET("/help", Help)
router.GET("/alerts.json", Alerts)
router.GET("/autocomplete.json", Autocomplete)
router.Run()
}

View File

@@ -31,12 +31,12 @@ func noCache(c *gin.Context) {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
}
// Index view, html
func Index(c *gin.Context) {
// index view, html
func index(c *gin.Context) {
start := time.Now()
cssFiles := ReadAssets("css")
jsFiles := ReadAssets("js")
cssFiles := readAssets("css")
jsFiles := readAssets("js")
noCache(c)
q, qPresent := c.GetQuery("q")
@@ -45,7 +45,7 @@ func Index(c *gin.Context) {
defaultUsed = false
}
c.HTML(http.StatusOK, "index.html", gin.H{
c.HTML(http.StatusOK, "templates/index.html", gin.H{
"Version": version,
"SentryDSN": config.Config.SentryPublicDSN,
"CSSFiles": cssFiles,
@@ -61,11 +61,11 @@ func Index(c *gin.Context) {
}
// Help view, html
func Help(c *gin.Context) {
func help(c *gin.Context) {
start := time.Now()
cssFiles := ReadAssets("css")
cssFiles := readAssets("css")
noCache(c)
c.HTML(http.StatusOK, "help.html", gin.H{
c.HTML(http.StatusOK, "templates/help.html", gin.H{
"CSSFiles": cssFiles,
"SentryDSN": config.Config.SentryPublicDSN,
})
@@ -76,8 +76,8 @@ func logAlertsView(c *gin.Context, cacheStatus string, duration time.Duration) {
log.Infof("[%s %s] <%d> %s %s took %s", c.ClientIP(), cacheStatus, http.StatusOK, c.Request.Method, c.Request.RequestURI, duration)
}
// Alerts endpoint, json, JS will query this via AJAX call
func Alerts(c *gin.Context) {
// alerts endpoint, json, JS will query this via AJAX call
func alerts(c *gin.Context) {
noCache(c)
start := time.Now()
ts, _ := start.UTC().MarshalText()
@@ -206,8 +206,8 @@ func Alerts(c *gin.Context) {
logAlertsView(c, "MIS", time.Since(start))
}
// Autocomplete endpoint, json, used for filter autocomplete hints
func Autocomplete(c *gin.Context) {
// autocomplete endpoint, json, used for filter autocomplete hints
func autocomplete(c *gin.Context) {
noCache(c)
start := time.Now()
@@ -260,3 +260,9 @@ func Autocomplete(c *gin.Context) {
c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte))
logAlertsView(c, "MIS", time.Since(start))
}
func favicon(c *gin.Context) {
fs := newBinaryFileSystem("static")
fileserver := http.FileServer(fs)
fileserver.ServeHTTP(c.Writer, c.Request)
}

View File

@@ -29,11 +29,7 @@ func mockConfig() {
func ginTestEngine() *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.New()
r.LoadHTMLGlob("templates/*")
r.GET("/", Index)
r.GET("/help", Help)
r.GET("/alerts.json", Alerts)
r.GET("/autocomplete.json", Autocomplete)
setupRouter(r)
return r
}