You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

178 lines
4.6 KiB

package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/spf13/pflag"
)
const version = "dev"
func main() {
addr := pflag.StringP("addr", "a", ":8080", "The address to listen on")
iconsDir := pflag.StringP("icons-dir", "i", "./icons", "The directory where icons are stored")
configFile := pflag.StringP("config-file", "c", "./services.conf", "The path to the config file")
help := pflag.BoolP("help", "h", false, "Show this help text")
envmap := map[string]string{
"addr": "DASHBOARD_ADDR",
"icons-dir": "DASHBOARD_ICONS_DIR",
"config-file": "DASHBOARD_CONFIG_FILE",
}
if val, ok := os.LookupEnv("PORT"); ok {
val = ":" + val
fmt.Printf("Setting --addr from PORT to %#v\n", val)
if err := pflag.Set("addr", val); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(2)
return
}
}
pflag.VisitAll(func(flag *pflag.Flag) {
if env, ok := envmap[flag.Name]; ok {
flag.Usage += fmt.Sprintf(" (env %s)", env)
if val, ok := os.LookupEnv(env); ok {
fmt.Printf("Setting --%s from %s to %#v\n", flag.Name, env, val)
if err := flag.Value.Set(val); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(2)
return
}
}
}
})
pflag.Parse()
if *help {
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\nVersion:\n dashboard %s\n\nOptions:\n", os.Args[0], version)
pflag.PrintDefaults()
os.Exit(0)
return
}
if fi, err := os.Stat(*iconsDir); err != nil {
fmt.Fprintf(os.Stderr, "Error: Icons dir %#v: %v.\n", *iconsDir, err)
os.Exit(1)
return
} else if !fi.IsDir() {
fmt.Fprintf(os.Stderr, "Error: Icons dir %#v: not a dir.\n", *iconsDir)
os.Exit(1)
return
}
f, err := os.OpenFile(*configFile, os.O_RDONLY, 0)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: Open config %#v: %v.\n", *configFile, err)
os.Exit(1)
return
}
c, err := ParseConfig(f)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: Parse config: %v.\n", err)
f.Close()
os.Exit(1)
return
}
f.Close()
if err := c.Validate(); err != nil {
fmt.Fprintf(os.Stderr, "Error: Validate config: %v.\n", err)
os.Exit(1)
return
}
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(middleware.RedirectSlashes)
r.Use(middlewareAuth(c))
r.Get("/", indexHandler(c))
r.Handle("/icons/*", http.StripPrefix("/icons", http.FileServer(http.Dir(*iconsDir))))
fmt.Printf("Listening on http://%s.\n", *addr)
if err := http.ListenAndServe(*addr, r); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v.\n", err)
os.Exit(1)
return
}
}
type ctxKey int
const (
_ ctxKey = iota
authProviderKey
authUserKey
)
// middlewareAuth process the authentication and will set authProviderKey to
// an empty string or the currently configured provider and authUserKey to an
// empty string or the currently validly authenticated username.
func middlewareAuth(c *Config) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
var authUser string
p, authProvider := c.Auth.GetProvider()
if p != nil {
switch p := p.(type) {
case AuthProviderJWT:
authUser, err = p.ParseJWT(r)
if err != nil {
ck, _ := r.Cookie(p.CookieName)
log.Printf("[%s] parse JWT %#v: %v", middleware.GetReqID(r.Context()), ck, err)
break
}
default:
panic(authProvider + " not implemented!")
}
}
ctx := r.Context()
ctx = context.WithValue(ctx, authProviderKey, authProvider)
ctx = context.WithValue(ctx, authUserKey, authUser)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// getAuth gets the valid claims parsed in middlewareAuth.
func getAuth(ctx context.Context) (authUser, authProvider string) {
authProvider = ctx.Value(authProviderKey).(string)
authUser = ctx.Value(authUserKey).(string)
return authProvider, authUser
}
func indexHandler(c *Config) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
purl := r.URL
purl.Host = r.Host
authProvider, authUser := getAuth(ctx)
log.Printf("[%s] url=%#v authProvider=%#v authUser=%#v", middleware.GetReqID(ctx), purl.String(), authProvider, authUser)
w.Header().Set("Cache-Control", "no-cache, private, max-age=0")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("X-Dashboard-Auth", authProvider+"::"+authUser)
w.WriteHeader(http.StatusOK)
RenderIndex(w, c.Filtered(authProvider, authUser), c.Auth, authUser, purl.String())
})
}