diff --git a/cmd/haproxy-lb/main.go b/cmd/haproxy-lb/main.go index 2e733e6..cebac49 100644 --- a/cmd/haproxy-lb/main.go +++ b/cmd/haproxy-lb/main.go @@ -3,6 +3,7 @@ package main import ( "errors" "flag" + "fmt" "os" "os/signal" "syscall" @@ -14,10 +15,12 @@ import ( "git.giftfish.de/ston1th/haproxy-lb/pkg/controller" "git.giftfish.de/ston1th/haproxy-lb/pkg/etcd" "git.giftfish.de/ston1th/haproxy-lb/pkg/raft" - "github.com/go-logr/logr" //"github.com/iand/logfmtr" - "git.giftfish.de/ston1th/logfmtr" + //"git.giftfish.de/ston1th/logfmtr" + + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" ) var ( @@ -34,42 +37,52 @@ func fatal(err error, msg string) { } func main() { - logopts := logfmtr.DefaultOptions() - logopts.TimestampFormat = "2006-01-02T15:04:05.000Z-0700" - logopts.AddCaller = true - logopts.CallerSkip = 1 - logopts.Timezone = true - logfmtr.UseOptions(logopts) + //logopts := logfmtr.DefaultOptions() + //logopts.TimestampFormat = "2006-01-02T15:04:05.000Z-0700" + //logopts.AddCaller = true + //logopts.CallerSkip = 1 + //logopts.Timezone = true + //logfmtr.UseOptions(logopts) flag.IntVar(&verbosity, "v", verbosity, "number for the log level verbosity") flag.StringVar(&configFile, "config", "haproxy-lb.yaml", "haproxy-lb config file") flag.Parse() - logfmtr.SetVerbosity(verbosity) - log = logfmtr.New().WithName("main") + //logfmtr.SetVerbosity(verbosity) + //log = logfmtr.New() - log.Info("starting haproxy-lb", "version", version) + log = funcr.New( + func(pfx, args string) { fmt.Println("bla", pfx, args) }, + funcr.Options{ + LogCaller: funcr.All, + LogTimestamp: true, + }, + ) + + l := log.WithName("main") + + l.Info("starting haproxy-lb", "version", version) cfg, err := config.ParseFile(configFile) if err != nil { - fatal(err, "init failed") + fatal(err, "config init failed") } var c cluster.Cluster if cfg.Cluster.Raft != nil { - log.Info("creating cluster", "mode", "raft") - c = raft.NewCluster(logfmtr.New().WithName("raft")) + l.Info("creating cluster", "mode", "raft") + c = raft.NewCluster(log.WithName("raft")) } else if cfg.Cluster.Etcd != nil { - log.Info("creating cluster", "mode", "etcd") - c = etcd.NewCluster(logfmtr.New().WithName("etcd")) + l.Info("creating cluster", "mode", "etcd") + c = etcd.NewCluster(log.WithName("etcd")) } - log.Info("starting api server", "address", cfg.APIServer.Listen, "tls", cfg.APIServer.TLS != nil, "auth", !cfg.APIServer.DisableAuth) - srv, err := api.NewServer(cfg, logfmtr.New().WithName("api")) + l.Info("starting api server", "address", cfg.APIServer.Listen, "tls", cfg.APIServer.TLS != nil, "auth", !cfg.APIServer.DisableAuth) + srv, err := api.NewServer(cfg, log.WithName("api")) if err != nil { fatal(err, "init failed") } - log.Info("starting vip-controller") - callbacks, err := controller.NewLBController(cfg, srv, logfmtr.New().WithName("vip-controller")) + l.Info("starting vip-controller") + callbacks, err := controller.NewLBController(cfg, srv, log.WithName("vip-controller")) if err != nil { fatal(err, "init failed") } @@ -81,7 +94,7 @@ func main() { for { err = c.Start(cfg) if err == cluster.ErrRestart { - log.Error(err, "received restart signal. restarting cluster controller.") + l.Error(err, "received restart signal. restarting cluster controller.") c.Reset() time.Sleep(time.Second) continue @@ -102,16 +115,16 @@ func main() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) s := <-sigs - log.Info("haproxy-lb shutdown", "signal", s.String()) + l.Info("haproxy-lb shutdown", "signal", s.String()) go func() { select { case <-time.After(time.Second * 20): - log.Error(errors.New("shutdown took longer than 20 seconds"), "shutdown forced") + l.Error(errors.New("shutdown took longer than 20 seconds"), "shutdown forced") case <-sigs: - log.Error(errors.New("force shutdown triggerd"), "shutdown forced") + l.Error(errors.New("force shutdown triggerd"), "shutdown forced") } os.Exit(1) }() c.Stop() - log.Info("haproxy-lb stopped") + l.Info("haproxy-lb stopped") } diff --git a/pkg/api/client/client.go b/pkg/api/client/client.go index 4685709..cea117a 100644 --- a/pkg/api/client/client.go +++ b/pkg/api/client/client.go @@ -23,6 +23,7 @@ type Client struct { endpoint string username string password string + auth bool httpClient *http.Client debugWriter io.Writer insecure bool @@ -62,6 +63,7 @@ func WithCredentials(username, password string) ClientOption { return func(client *Client) { client.username = username client.password = password + client.auth = true } } @@ -77,6 +79,7 @@ var ( type Config struct { Endpoint string `json:"endpoint"` + NoAuth bool `json:"no_auth"` User string `json:"user"` Password string `json:"password"` Insecure bool `json:"insecure"` @@ -87,6 +90,13 @@ func ClientOptionsFromConfig(cfg Config) (options []ClientOption, err error) { err = ErrMissingEndpoint return } + options = []ClientOption{ + WithEndpoint(cfg.Endpoint), + WithInsecureClient(cfg.Insecure), + } + if cfg.NoAuth { + return + } if cfg.User == "" { err = ErrMissingUser return @@ -95,11 +105,7 @@ func ClientOptionsFromConfig(cfg Config) (options []ClientOption, err error) { err = ErrMissingPassword return } - options = []ClientOption{ - WithEndpoint(cfg.Endpoint), - WithCredentials(cfg.User, cfg.Password), - WithInsecureClient(cfg.Insecure), - } + options = append(options, WithCredentials(cfg.User, cfg.Password)) return } @@ -109,6 +115,17 @@ func ClientOptionsFromEnv() (options []ClientOption, err error) { err = ErrMissingEndpointEnv return } + insecure, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_INSECURE")) + + options = []ClientOption{ + WithEndpoint(endpoint), + WithInsecureClient(insecure), + } + noAuth, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_NO_AUTH")) + if noAuth { + return + } + user := os.Getenv("HAPROXY_LB_USER") if user == "" { err = ErrMissingUserEnv @@ -121,13 +138,7 @@ func ClientOptionsFromEnv() (options []ClientOption, err error) { return } - insecure, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_INSECURE")) - - options = []ClientOption{ - WithEndpoint(endpoint), - WithCredentials(user, pass), - WithInsecureClient(insecure), - } + options = append(options, WithCredentials(user, pass)) return } @@ -163,7 +174,9 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re if err != nil { return nil, err } - req.SetBasicAuth(c.username, c.password) + if c.auth { + req.SetBasicAuth(c.username, c.password) + } req = req.WithContext(ctx) return req, nil } diff --git a/pkg/api/client/helper.go b/pkg/api/client/helper.go index 5dc396b..e1958e6 100644 --- a/pkg/api/client/helper.go +++ b/pkg/api/client/helper.go @@ -2,13 +2,16 @@ package client import ( "encoding/json" - "errors" "net/http" ) type Error struct { - Message string `json:"message"` - Status int `json:"status"` + Err string `json:"error"` + Status int `json:"status"` +} + +func (e Error) Error() string { + return e.Err } func decodeResp(resp *http.Response, v interface{}) error { @@ -22,16 +25,13 @@ func decodeResp(resp *http.Response, v interface{}) error { } return json.NewDecoder(resp.Body).Decode(&v) } - if resp.StatusCode == 404 { - return ErrNotFound - } var e Error err := json.NewDecoder(resp.Body).Decode(&e) if err != nil { return err } if resp.StatusCode >= 400 && resp.StatusCode <= 599 { - return errors.New(e.Message) + return e } return nil } diff --git a/pkg/api/server.go b/pkg/api/server.go index 89b5e6c..aa67d1e 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -43,7 +43,7 @@ type notFoundHandler struct { } func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - types.NewContext(w, r, nf.s.Data, nf.s.log).NotFound() + types.NewContext(w, r, nf.s.Data, nf.s.log).Err(types.ErrInvalidAPIRoute) } // NewHTTPServer returns a new HTTPServer diff --git a/pkg/api/types/context.go b/pkg/api/types/context.go index 29d89be..b5567bd 100644 --- a/pkg/api/types/context.go +++ b/pkg/api/types/context.go @@ -86,7 +86,7 @@ func (c *Context) log() { c.Log.Info("access", "addr", c.Request.RemoteAddr, "method", c.Request.Method, - "url", c.Request.URL.Path, + "path", c.Request.URL.Path, "status", c.Status, ) } diff --git a/pkg/api/types/error.go b/pkg/api/types/error.go index 700aad1..cbe583c 100644 --- a/pkg/api/types/error.go +++ b/pkg/api/types/error.go @@ -19,7 +19,7 @@ type Err struct { // Error returns the error string func (a Err) Error() string { - return `{"message":"` + a.err + `","status":` + strconv.Itoa(a.status) + `}` + return `{"error":"` + a.err + `","status":` + strconv.Itoa(a.status) + `}` } // Status returns the HTTP status code @@ -35,13 +35,17 @@ func newError(err string, status int) Error { } var ( - ErrBadreq = newError("bad request", http.StatusBadRequest) - ErrInvalid = newError("invalid data", http.StatusBadRequest) - ErrUnauthorized = newError("unauthorized", http.StatusUnauthorized) - ErrForbidden = newError("forbidden", http.StatusForbidden) - ErrNotFound = newError("not found", http.StatusNotFound) - ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed) - ErrExists = newError("resource already exists", http.StatusConflict) - ErrISE = newError("internal server error", http.StatusInternalServerError) - ErrFollower = newError("follower write forbidden", http.StatusForbidden) + ErrInvalidAPIRoute = newError("invalid api route", http.StatusNotFound) + ErrBadreq = newError("bad request", http.StatusBadRequest) + ErrInvalid = newError("invalid data", http.StatusBadRequest) + ErrUnauthorized = newError("unauthorized", http.StatusUnauthorized) + ErrForbidden = newError("forbidden", http.StatusForbidden) + ErrNotFound = newError("not found", http.StatusNotFound) + ErrLBNotFound = newError("loadbalancer not found", http.StatusNotFound) + ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed) + ErrExists = newError("resource already exists", http.StatusConflict) + ErrISE = newError("internal server error", http.StatusInternalServerError) + ErrFollower = newError("follower write forbidden", http.StatusForbidden) + ErrClusterNotFound = newError("cluster not found", http.StatusNotFound) + ErrLBsNotFound = newError("loadbalancers not found", http.StatusNotFound) ) diff --git a/pkg/api/v1/client/client.go b/pkg/api/v1/client/client.go index 0c2b4b1..d83ed83 100644 --- a/pkg/api/v1/client/client.go +++ b/pkg/api/v1/client/client.go @@ -13,7 +13,7 @@ import ( var ( ErrClientIsNil = errors.New("client is nil") - base = "/" + schema.Version + base = schema.LBPath ) type Client struct { @@ -32,7 +32,7 @@ func (c *Client) GetLoadBalancers(ctx context.Context) (lbs map[string]schema.Lo } func (c *Client) GetLoadBalancersWithCluster(ctx context.Context, cluster string) (lbs map[string]schema.LoadBalancer, err error) { - path := base + "/lb" + path := base if cluster != "" { path += "/" + cluster } @@ -46,7 +46,7 @@ func (c *Client) GetLoadBalancersWithCluster(ctx context.Context, cluster string } func (c *Client) GetLoadBalancer(ctx context.Context, cluster, name string) (lb schema.LoadBalancer, err error) { - path := fmt.Sprintf("%s/lb/%s/%s", base, cluster, name) + path := fmt.Sprintf("%s/%s/%s", base, cluster, name) req, err := c.c.NewRequest(ctx, "GET", path, nil) if err != nil { return @@ -57,7 +57,7 @@ func (c *Client) GetLoadBalancer(ctx context.Context, cluster, name string) (lb func (c *Client) CreateLoadBalancer(ctx context.Context, cluster, name string, lb schema.LoadBalancer) (err error) { buf := new(bytes.Buffer) - path := fmt.Sprintf("%s/lb/%s/%s", base, cluster, name) + path := fmt.Sprintf("%s/%s/%s", base, cluster, name) err = lb.ValidateClient() if err != nil { return @@ -75,7 +75,7 @@ func (c *Client) CreateLoadBalancer(ctx context.Context, cluster, name string, l } func (c *Client) DeleteLoadBalancer(ctx context.Context, cluster, name string) (err error) { - path := fmt.Sprintf("%s/lb/%s/%s", base, cluster, name) + path := fmt.Sprintf("%s/%s/%s", base, cluster, name) req, err := c.c.NewRequest(ctx, "DELETE", path, nil) if err != nil { return @@ -88,7 +88,7 @@ func (c *Client) DeleteLoadBalancersWithCluster(ctx context.Context, cluster str if cluster == "" { return errors.New("cluster value can not be empty") } - path := base + "/lb/" + cluster + path := base + "/" + cluster req, err := c.c.NewRequest(ctx, "DELETE", path, nil) if err != nil { return diff --git a/pkg/api/v1/schema/schema.go b/pkg/api/v1/schema/schema.go index 7b40c58..28923dc 100644 --- a/pkg/api/v1/schema/schema.go +++ b/pkg/api/v1/schema/schema.go @@ -12,7 +12,11 @@ healthCheckNodePort */ -const Version = "v1" +const ( + Version = "v1" + LBName = "lb" + LBPath = "/" + Version + "/" + LBName +) type ProxyProtocol string diff --git a/pkg/api/v1/server/handler.go b/pkg/api/v1/server/handler.go index a5fcdab..b724845 100644 --- a/pkg/api/v1/server/handler.go +++ b/pkg/api/v1/server/handler.go @@ -62,7 +62,7 @@ func lbHandler(ctx *types.Context) { if err != nil { ctx.Log.Error(err, "error reading loadbalancer", "cluster", cl, "name", n) if err == cluster.ErrKeyNotFound { - ctx.Err(types.ErrNotFound) + ctx.Err(types.ErrLBNotFound) return } ctx.Err(types.ErrISE) @@ -139,7 +139,7 @@ func lbHandler(ctx *types.Context) { return } if !ctx.Data.DB.LBExists(name) { - ctx.Err(types.ErrNotFound) + ctx.Err(types.ErrLBNotFound) return } cidr, err := ctx.Data.DB.GetCIDR(name) @@ -175,6 +175,10 @@ func lbList(lbs map[string][]byte) (m map[string]json.RawMessage) { func lbClusterHandler(ctx *types.Context) { cl := ctx.Var("cluster") m, err := ctx.Data.DB.GetLBs(cl) + if err == cluster.ErrPrefixNotFound { + ctx.Err(types.ErrClusterNotFound) + return + } if err != nil { ctx.Log.Error(err, "error reading loadbalancers", "cluster", cl) ctx.Err(types.ErrISE) @@ -214,6 +218,10 @@ func lbClusterHandler(ctx *types.Context) { } func lbListHandler(ctx *types.Context) { m, err := ctx.Data.DB.GetLBs("") + if err == cluster.ErrPrefixNotFound { + ctx.Err(types.ErrLBsNotFound) + return + } if err != nil { ctx.Log.Error(err, "error reading loadbalancer clusters") ctx.Err(types.ErrISE) diff --git a/pkg/api/v1/server/routes.go b/pkg/api/v1/server/routes.go index 8f7cd79..f378b19 100644 --- a/pkg/api/v1/server/routes.go +++ b/pkg/api/v1/server/routes.go @@ -2,29 +2,33 @@ package server import ( "git.giftfish.de/ston1th/haproxy-lb/pkg/api/types" - schemav1 "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/schema" + "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/schema" ) -const v1 = "/" + schemav1.Version +const ( + healthz = "/healthz" + lbcluster = schema.LBPath + "/{cluster:[a-zA-Z0-9-]+$}" + lbclustername = schema.LBPath + "/{cluster:[a-zA-Z0-9-]+}/{name:[a-zA-Z0-9-_]+$}" +) var Routes = []types.Route{ { - "/healthz", + healthz, healthzHandler, []string{"GET"}, }, { - v1 + "/lb", + schema.LBPath, authHandler(lbListHandler), []string{"GET"}, }, { - v1 + "/lb/{cluster:[a-zA-Z0-9-]+$}", + lbcluster, authHandler(lbClusterHandler), []string{"GET", "DELETE"}, }, { - v1 + "/lb/{cluster:[a-zA-Z0-9-]+}/{name:[a-zA-Z0-9-_]+$}", + lbclustername, authHandler(lbHandler), []string{"GET", "POST", "DELETE"}, }, diff --git a/pkg/db/db.go b/pkg/db/db.go index 5eea27b..e4ffff6 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -86,10 +86,6 @@ func (db *DB) GetLBs(cl string) (lbs map[string][]byte, err error) { cl = cl + "/" } m, err := db.kv.GetPrefix(lbPrefix + cl) - if err == cluster.ErrPrefixNotFound { - err = nil - return - } if err != nil { return }