diff --git a/CHANGELOG.md b/CHANGELOG.md index 991055af1..df29cef89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan ### Changed +- [#82](https://github.com/kobsio/kobs/pull/82): Improve error handling for our API. + ## [v0.4.0](https://github.com/kobsio/kobs/releases/tag/v0.4.0) (2021-07-14) ### Added diff --git a/pkg/api/clusters/router.go b/pkg/api/clusters/router.go index 741403423..28b76b483 100644 --- a/pkg/api/clusters/router.go +++ b/pkg/api/clusters/router.go @@ -53,13 +53,13 @@ func (router *Router) getNamespaces(w http.ResponseWriter, r *http.Request) { for _, clusterName := range clusterNames { cluster := router.clusters.GetCluster(clusterName) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } clusterNamespaces, err := cluster.GetNamespaces(r.Context(), cacheDurationNamespaces) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get namespaces")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get namespaces") return } diff --git a/pkg/api/middleware/errresponse/errresponse.go b/pkg/api/middleware/errresponse/errresponse.go index 7a6a44453..c72b05fc6 100644 --- a/pkg/api/middleware/errresponse/errresponse.go +++ b/pkg/api/middleware/errresponse/errresponse.go @@ -3,35 +3,31 @@ package errresponse import ( + "fmt" "net/http" + "github.com/kobsio/kobs/pkg/api/middleware/httplog" + "github.com/go-chi/render" ) // ErrResponse renderer type for handling all sorts of errors. type ErrResponse struct { - Err error `json:"-"` // low-level runtime error - HTTPStatusCode int `json:"-"` // http response status code - StatusText string `json:"error"` // user-level status message -} - -func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { - render.Status(r, e.HTTPStatusCode) - return nil + Error string `json:"error"` } -func Render(err error, status int, msg string) render.Renderer { +// Render sets the given status for the response and then returns the error and message as JSON object. +func Render(w http.ResponseWriter, r *http.Request, err error, status int, msg string) { if err != nil { - return &ErrResponse{ - Err: err, - HTTPStatusCode: status, - StatusText: msg, - } + msg = fmt.Sprintf("%s: %s", msg, err.Error()) } - return &ErrResponse{ - Err: nil, - HTTPStatusCode: status, - StatusText: msg, + errResponse := &ErrResponse{ + Error: msg, } + + httplog.LogEntrySetField(r, "error", msg) + + render.Status(r, status) + render.JSON(w, r, errResponse) } diff --git a/plugins/applications/applications.go b/plugins/applications/applications.go index 8fb13ea42..28bb12903 100644 --- a/plugins/applications/applications.go +++ b/plugins/applications/applications.go @@ -81,7 +81,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { return } - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not get applications")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not get applications") return } @@ -108,14 +108,14 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { for _, clusterName := range clusterNames { cluster := router.clusters.GetCluster(clusterName) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } for _, namespace := range namespaces { application, err := cluster.GetApplications(r.Context(), namespace) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get applications")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get applications") return } @@ -153,7 +153,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { return } - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not generate topology")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not generate topology") return } @@ -172,7 +172,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { return } - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid view property")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid view property") } // getApplication returns a a single application for the given clusters and namespaces and name. The cluster, namespace @@ -186,13 +186,13 @@ func (router *Router) getApplication(w http.ResponseWriter, r *http.Request) { cluster := router.clusters.GetCluster(clusterName) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } application, err := cluster.GetApplication(r.Context(), namespace, name) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get application")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get application") return } diff --git a/plugins/dashboards/dashboards.go b/plugins/dashboards/dashboards.go index 1e332b585..8c9c98383 100644 --- a/plugins/dashboards/dashboards.go +++ b/plugins/dashboards/dashboards.go @@ -58,7 +58,7 @@ func (router *Router) getAllDashboards(w http.ResponseWriter, r *http.Request) { for _, cluster := range router.clusters.Clusters { dashboard, err := cluster.GetDashboards(r.Context(), "") if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get dashboards")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get dashboards") return } @@ -79,7 +79,7 @@ func (router *Router) getDashboards(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&data) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not decode request body")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not decode request body") return } @@ -100,20 +100,20 @@ func (router *Router) getDashboards(w http.ResponseWriter, r *http.Request) { cluster := router.clusters.GetCluster(reference.Cluster) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } dashboard, err := cluster.GetDashboard(r.Context(), reference.Namespace, reference.Name) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get dashboard")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get dashboard") return } if reference.Placeholders != nil { dashboard, err = placeholders.Replace(reference.Placeholders, *dashboard) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not replace placeholders")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not replace placeholders") return } } @@ -136,7 +136,7 @@ func (router *Router) getDashboard(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&data) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not decode request body")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not decode request body") return } @@ -144,20 +144,20 @@ func (router *Router) getDashboard(w http.ResponseWriter, r *http.Request) { cluster := router.clusters.GetCluster(data.Cluster) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } dashboard, err := cluster.GetDashboard(r.Context(), data.Namespace, data.Name) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get dashboard")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get dashboard") return } if data.Placeholders != nil { dashboard, err = placeholders.Replace(data.Placeholders, *dashboard) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not replace placeholders")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not replace placeholders") return } } diff --git a/plugins/elasticsearch/elasticsearch.go b/plugins/elasticsearch/elasticsearch.go index ad5180092..2e72c1bc5 100644 --- a/plugins/elasticsearch/elasticsearch.go +++ b/plugins/elasticsearch/elasticsearch.go @@ -57,25 +57,25 @@ func (router *Router) getLogs(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } parsedTimeStart, err := strconv.ParseInt(timeStart, 10, 64) if err != nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not parse start time")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not parse start time") return } parsedTimeEnd, err := strconv.ParseInt(timeEnd, 10, 64) if err != nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not parse end time")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not parse end time") return } data, err := i.GetLogs(r.Context(), query, scrollID, parsedTimeStart, parsedTimeEnd) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not get logs")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get logs") return } diff --git a/plugins/jaeger/jaeger.go b/plugins/jaeger/jaeger.go index e3c68ee3b..4791c2492 100644 --- a/plugins/jaeger/jaeger.go +++ b/plugins/jaeger/jaeger.go @@ -48,13 +48,13 @@ func (router *Router) getServices(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } body, err := i.GetServices() if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not get services")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get services") return } @@ -69,13 +69,13 @@ func (router *Router) getOperations(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } body, err := i.GetOperations(service) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not get operations")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get operations") return } @@ -97,25 +97,25 @@ func (router *Router) getTraces(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } parsedTimeStart, err := strconv.ParseInt(timeStart, 10, 64) if err != nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not parse start time")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not parse start time") return } parsedTimeEnd, err := strconv.ParseInt(timeEnd, 10, 64) if err != nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not parse end time")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not parse end time") return } body, err := i.GetTraces(limit, maxDuration, minDuration, operation, service, tags, parsedTimeStart, parsedTimeEnd) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not get traces")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get traces") return } @@ -130,13 +130,13 @@ func (router *Router) getTrace(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } body, err := i.GetTrace(traceID) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not get trace")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get trace") return } diff --git a/plugins/prometheus/prometheus.go b/plugins/prometheus/prometheus.go index 87f8a61a0..f41c26294 100644 --- a/plugins/prometheus/prometheus.go +++ b/plugins/prometheus/prometheus.go @@ -71,7 +71,7 @@ func (router *Router) getVariable(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } @@ -79,13 +79,13 @@ func (router *Router) getVariable(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&data) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not decode request body")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not decode request body") return } values, err := i.GetVariable(r.Context(), data.Label, data.Query, data.Type, data.TimeStart, data.TimeEnd) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get variable")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get variable") return } @@ -103,7 +103,7 @@ func (router *Router) getMetrics(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } @@ -111,13 +111,13 @@ func (router *Router) getMetrics(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&data) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not decode request body")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not decode request body") return } metrics, err := i.GetMetrics(r.Context(), data.Queries, data.Resolution, data.TimeStart, data.TimeEnd) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get metrics")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get metrics") return } @@ -135,7 +135,7 @@ func (router *Router) getTable(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } @@ -143,13 +143,13 @@ func (router *Router) getTable(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&data) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not decode request body")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not decode request body") return } rows, err := i.GetTableData(r.Context(), data.Queries, data.TimeEnd) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get metrics")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get metrics") return } @@ -167,13 +167,13 @@ func (router *Router) getLabels(w http.ResponseWriter, r *http.Request) { i := router.getInstance(name) if i == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "could not find instance name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name") return } labelValues, err := i.GetLabelValues(r.Context(), searchTerm) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not get label values")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get label values") return } diff --git a/plugins/resources/resources.go b/plugins/resources/resources.go index 253c25ea5..6a4a5cb63 100644 --- a/plugins/resources/resources.go +++ b/plugins/resources/resources.go @@ -74,12 +74,12 @@ func (router *Router) getResources(w http.ResponseWriter, r *http.Request) { for _, clusterName := range clusterNames { cluster := router.clusters.GetCluster(clusterName) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } if router.isForbidden(resource) { - render.Render(w, r, errresponse.Render(nil, http.StatusForbidden, fmt.Sprintf("access for resource %s is forbidding", resource))) + errresponse.Render(w, r, nil, http.StatusForbidden, fmt.Sprintf("Access for resource %s is forbidding", resource)) return } @@ -89,14 +89,14 @@ func (router *Router) getResources(w http.ResponseWriter, r *http.Request) { if namespaces == nil { list, err := cluster.GetResources(r.Context(), "", path, resource, paramName, param) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get resources")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get resources") return } var tmpResources map[string]interface{} err = json.Unmarshal(list, &tmpResources) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not unmarshal resources")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not unmarshal resources") return } @@ -109,14 +109,14 @@ func (router *Router) getResources(w http.ResponseWriter, r *http.Request) { for _, namespace := range namespaces { list, err := cluster.GetResources(r.Context(), namespace, path, resource, paramName, param) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get resources")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get resources") return } var tmpResources map[string]interface{} err = json.Unmarshal(list, &tmpResources) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusInternalServerError, "could not unmarshal resources")) + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not unmarshal resources") return } @@ -147,25 +147,25 @@ func (router *Router) getLogs(w http.ResponseWriter, r *http.Request) { cluster := router.clusters.GetCluster(clusterName) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } parsedSince, err := strconv.ParseInt(since, 10, 64) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not parse since parameter")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not parse since parameter") return } parsedPrevious, err := strconv.ParseBool(previous) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not parse previous parameter")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not parse previous parameter") return } logs, err := cluster.GetLogs(r.Context(), namespace, name, container, parsedSince, parsedPrevious) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadGateway, "could not get logs")) + errresponse.Render(w, r, err, http.StatusBadGateway, "Could not get logs") return } diff --git a/plugins/teams/teams.go b/plugins/teams/teams.go index 51f45ac22..4904ad28f 100644 --- a/plugins/teams/teams.go +++ b/plugins/teams/teams.go @@ -40,7 +40,7 @@ func (router *Router) getTeams(w http.ResponseWriter, r *http.Request) { for _, cluster := range router.clusters.Clusters { team, err := cluster.GetTeams(r.Context(), "") if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get teams")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get teams") return } @@ -63,13 +63,13 @@ func (router *Router) getTeam(w http.ResponseWriter, r *http.Request) { cluster := router.clusters.GetCluster(clusterName) if cluster == nil { - render.Render(w, r, errresponse.Render(nil, http.StatusBadRequest, "invalid cluster name")) + errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } team, err := cluster.GetTeam(r.Context(), namespace, name) if err != nil { - render.Render(w, r, errresponse.Render(err, http.StatusBadRequest, "could not get team")) + errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get team") return }