influxdb/http/check_service.go

609 lines
17 KiB
Go
Raw Normal View History

2019-07-25 10:30:25 +00:00
package http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/influxdata/influxdb"
pctx "github.com/influxdata/influxdb/context"
2019-07-25 10:30:25 +00:00
"github.com/influxdata/influxdb/notification/check"
"github.com/julienschmidt/httprouter"
"go.uber.org/zap"
)
// CheckBackend is all services and associated parameters required to construct
// the CheckBackendHandler.
type CheckBackend struct {
influxdb.HTTPErrorHandler
Logger *zap.Logger
2019-09-18 20:19:51 +00:00
TaskService influxdb.TaskService
2019-07-25 10:30:25 +00:00
CheckService influxdb.CheckService
UserResourceMappingService influxdb.UserResourceMappingService
LabelService influxdb.LabelService
UserService influxdb.UserService
OrganizationService influxdb.OrganizationService
}
// NewCheckBackend returns a new instance of CheckBackend.
func NewCheckBackend(b *APIBackend) *CheckBackend {
return &CheckBackend{
HTTPErrorHandler: b.HTTPErrorHandler,
Logger: b.Logger.With(zap.String("handler", "check")),
2019-09-18 20:19:51 +00:00
TaskService: b.TaskService,
2019-07-25 10:30:25 +00:00
CheckService: b.CheckService,
UserResourceMappingService: b.UserResourceMappingService,
LabelService: b.LabelService,
UserService: b.UserService,
OrganizationService: b.OrganizationService,
}
}
// CheckHandler is the handler for the check service
type CheckHandler struct {
*httprouter.Router
influxdb.HTTPErrorHandler
Logger *zap.Logger
2019-09-18 20:19:51 +00:00
TaskService influxdb.TaskService
2019-07-25 10:30:25 +00:00
CheckService influxdb.CheckService
UserResourceMappingService influxdb.UserResourceMappingService
LabelService influxdb.LabelService
UserService influxdb.UserService
OrganizationService influxdb.OrganizationService
}
const (
checksPath = "/api/v2/checks"
checksIDPath = "/api/v2/checks/:id"
2019-08-22 16:18:02 +00:00
checksIDQueryPath = "/api/v2/checks/:id/query"
2019-07-25 10:30:25 +00:00
checksIDMembersPath = "/api/v2/checks/:id/members"
checksIDMembersIDPath = "/api/v2/checks/:id/members/:userID"
checksIDOwnersPath = "/api/v2/checks/:id/owners"
checksIDOwnersIDPath = "/api/v2/checks/:id/owners/:userID"
checksIDLabelsPath = "/api/v2/checks/:id/labels"
checksIDLabelsIDPath = "/api/v2/checks/:id/labels/:lid"
)
// NewCheckHandler returns a new instance of CheckHandler.
func NewCheckHandler(b *CheckBackend) *CheckHandler {
h := &CheckHandler{
Router: NewRouter(b.HTTPErrorHandler),
HTTPErrorHandler: b.HTTPErrorHandler,
Logger: b.Logger,
CheckService: b.CheckService,
UserResourceMappingService: b.UserResourceMappingService,
LabelService: b.LabelService,
UserService: b.UserService,
2019-09-18 20:19:51 +00:00
TaskService: b.TaskService,
2019-07-25 10:30:25 +00:00
OrganizationService: b.OrganizationService,
}
h.HandlerFunc("POST", checksPath, h.handlePostCheck)
h.HandlerFunc("GET", checksPath, h.handleGetChecks)
h.HandlerFunc("GET", checksIDPath, h.handleGetCheck)
2019-08-22 16:18:02 +00:00
h.HandlerFunc("GET", checksIDQueryPath, h.handleGetCheckQuery)
2019-07-25 10:30:25 +00:00
h.HandlerFunc("DELETE", checksIDPath, h.handleDeleteCheck)
h.HandlerFunc("PUT", checksIDPath, h.handlePutCheck)
h.HandlerFunc("PATCH", checksIDPath, h.handlePatchCheck)
memberBackend := MemberBackend{
HTTPErrorHandler: b.HTTPErrorHandler,
Logger: b.Logger.With(zap.String("handler", "member")),
ResourceType: influxdb.ChecksResourceType,
UserType: influxdb.Member,
UserResourceMappingService: b.UserResourceMappingService,
UserService: b.UserService,
}
h.HandlerFunc("POST", checksIDMembersPath, newPostMemberHandler(memberBackend))
h.HandlerFunc("GET", checksIDMembersPath, newGetMembersHandler(memberBackend))
h.HandlerFunc("DELETE", checksIDMembersIDPath, newDeleteMemberHandler(memberBackend))
ownerBackend := MemberBackend{
HTTPErrorHandler: b.HTTPErrorHandler,
Logger: b.Logger.With(zap.String("handler", "member")),
ResourceType: influxdb.ChecksResourceType,
UserType: influxdb.Owner,
UserResourceMappingService: b.UserResourceMappingService,
UserService: b.UserService,
}
h.HandlerFunc("POST", checksIDOwnersPath, newPostMemberHandler(ownerBackend))
h.HandlerFunc("GET", checksIDOwnersPath, newGetMembersHandler(ownerBackend))
h.HandlerFunc("DELETE", checksIDOwnersIDPath, newDeleteMemberHandler(ownerBackend))
labelBackend := &LabelBackend{
HTTPErrorHandler: b.HTTPErrorHandler,
Logger: b.Logger.With(zap.String("handler", "label")),
LabelService: b.LabelService,
ResourceType: influxdb.TelegrafsResourceType,
}
h.HandlerFunc("GET", checksIDLabelsIDPath, newGetLabelsHandler(labelBackend))
h.HandlerFunc("POST", checksIDLabelsPath, newPostLabelHandler(labelBackend))
h.HandlerFunc("DELETE", checksIDLabelsIDPath, newDeleteLabelHandler(labelBackend))
return h
}
type checkLinks struct {
Self string `json:"self"`
Labels string `json:"labels"`
Members string `json:"members"`
Owners string `json:"owners"`
}
type checkResponse struct {
influxdb.Check
2019-09-18 20:19:51 +00:00
Status string `json:"status"`
2019-07-25 10:30:25 +00:00
Labels []influxdb.Label `json:"labels"`
Links checkLinks `json:"links"`
}
func (resp checkResponse) MarshalJSON() ([]byte, error) {
b1, err := json.Marshal(resp.Check)
if err != nil {
return nil, err
}
b2, err := json.Marshal(struct {
Labels []influxdb.Label `json:"labels"`
Links checkLinks `json:"links"`
2019-09-18 20:19:51 +00:00
Status string `json:"status"`
2019-07-25 10:30:25 +00:00
}{
Links: resp.Links,
Labels: resp.Labels,
2019-09-18 20:19:51 +00:00
Status: resp.Status,
2019-07-25 10:30:25 +00:00
})
if err != nil {
return nil, err
}
return []byte(string(b1[:len(b1)-1]) + ", " + string(b2[1:])), nil
}
type checksResponse struct {
Checks []*checkResponse `json:"checks"`
Links *influxdb.PagingLinks `json:"links"`
}
2019-09-18 20:19:51 +00:00
func (h *CheckHandler) newCheckResponse(ctx context.Context, chk influxdb.Check, labels []*influxdb.Label) (*checkResponse, error) {
feat(checks): add first pass at creating tasks from checks First pass at flux AST generation from check Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): format call expression Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): cleanup CheckDefinition Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): clean up threshold functions Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): clean up message function Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): misc fixes Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): remove dead code Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): move threshold flux generation to check pkg Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): move base ast generation to its own package Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> fix(notification/check): add comment for GenerateFluxAST Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> docs(notification/flux): add comments to each exported function Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> feat(notification/check): add tests for GenerateFlux Co-authored-by: Michael Desa <mjdesa@gmail.com> Co-authored-by: Deniz Kusefoglu <deniz@influxdata.com> feat(notification/check): add task options to generated flux fix(notification/check): use flux compatible duration type test(notification/check): add task option to task definition test(http): use check Duration in checks http handlers feat(check): add TaskID to checks base fix(notification/check): hack around issue with formatting ast package wtih multiple files test(check): create task when check is created A lot of little changes had to happen as a result of this. This change was rather painful. feat(checks): add update and delete of task for check fix(notifications/check): hack around the alerts package not being available test(kv): temporarily skip check tests while we merge the pr above
2019-08-07 22:34:07 +00:00
// Ensure that we don't expose that this creates a task behind the scene
chk.ClearPrivateData()
2019-07-25 10:30:25 +00:00
res := &checkResponse{
Check: chk,
Links: checkLinks{
Self: fmt.Sprintf("/api/v2/checks/%s", chk.GetID()),
Labels: fmt.Sprintf("/api/v2/checks/%s/labels", chk.GetID()),
Members: fmt.Sprintf("/api/v2/checks/%s/members", chk.GetID()),
Owners: fmt.Sprintf("/api/v2/checks/%s/owners", chk.GetID()),
},
Labels: []influxdb.Label{},
}
for _, l := range labels {
res.Labels = append(res.Labels, *l)
}
2019-09-18 20:19:51 +00:00
task, err := h.TaskService.FindTaskByID(ctx, chk.GetTaskID())
if err != nil {
return nil, err
}
res.Status = task.Status
return res, nil
2019-07-25 10:30:25 +00:00
}
2019-09-18 20:19:51 +00:00
func (h *CheckHandler) newChecksResponse(ctx context.Context, chks []influxdb.Check, labelService influxdb.LabelService, f influxdb.PagingFilter, opts influxdb.FindOptions) *checksResponse {
2019-07-25 10:30:25 +00:00
resp := &checksResponse{
2019-09-18 20:19:51 +00:00
Checks: []*checkResponse{},
2019-07-25 10:30:25 +00:00
Links: newPagingLinks(checksPath, opts, f, len(chks)),
}
2019-09-18 20:19:51 +00:00
for _, chk := range chks {
2019-07-25 10:30:25 +00:00
labels, _ := labelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: chk.GetID()})
2019-09-18 20:19:51 +00:00
cr, err := h.newCheckResponse(ctx, chk, labels)
if err != nil {
h.Logger.Info("Failed to retrieve task associated with check", zap.String("checkID", chk.GetID().String()))
continue
}
resp.Checks = append(resp.Checks, cr)
2019-07-25 10:30:25 +00:00
}
return resp
}
func decodeGetCheckRequest(ctx context.Context, r *http.Request) (i influxdb.ID, err error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return i, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "url missing id",
}
}
if err := i.DecodeFromString(id); err != nil {
return i, err
}
return i, nil
}
func (h *CheckHandler) handleGetChecks(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
h.Logger.Debug("checks retrieve request", zap.String("r", fmt.Sprint(r)))
filter, opts, err := decodeCheckFilter(ctx, r)
if err != nil {
h.Logger.Debug("failed to decode request", zap.Error(err))
h.HandleHTTPError(ctx, err, w)
return
}
chks, _, err := h.CheckService.FindChecks(ctx, *filter, *opts)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
h.Logger.Debug("checks retrieved", zap.String("checks", fmt.Sprint(chks)))
2019-09-18 20:19:51 +00:00
if err := encodeResponse(ctx, w, http.StatusOK, h.newChecksResponse(ctx, chks, h.LabelService, filter, *opts)); err != nil {
2019-07-25 10:30:25 +00:00
logEncodingError(h.Logger, r, err)
return
}
}
2019-08-22 16:18:02 +00:00
func (h *CheckHandler) handleGetCheckQuery(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, err := decodeGetCheckRequest(ctx, r)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
chk, err := h.CheckService.FindCheckByID(ctx, id)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
flux, err := chk.GenerateFlux()
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
h.Logger.Debug("check query retrieved", zap.String("check query", flux))
if err := encodeResponse(ctx, w, http.StatusOK, newFluxResponse(flux)); err != nil {
logEncodingError(h.Logger, r, err)
return
}
}
type fluxResp struct {
Flux string `json:"flux"`
}
func newFluxResponse(flux string) fluxResp {
return fluxResp{
Flux: flux,
}
}
2019-07-25 10:30:25 +00:00
func (h *CheckHandler) handleGetCheck(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
h.Logger.Debug("check retrieve request", zap.String("r", fmt.Sprint(r)))
id, err := decodeGetCheckRequest(ctx, r)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
chk, err := h.CheckService.FindCheckByID(ctx, id)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
h.Logger.Debug("check retrieved", zap.String("check", fmt.Sprint(chk)))
labels, err := h.LabelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: chk.GetID()})
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
2019-09-18 20:19:51 +00:00
cr, err := h.newCheckResponse(ctx, chk, labels)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, cr); err != nil {
2019-07-25 10:30:25 +00:00
logEncodingError(h.Logger, r, err)
return
}
}
func decodeCheckFilter(ctx context.Context, r *http.Request) (*influxdb.CheckFilter, *influxdb.FindOptions, error) {
f := &influxdb.CheckFilter{}
opts, err := decodeFindOptions(ctx, r)
if err != nil {
return f, nil, err
}
q := r.URL.Query()
if orgIDStr := q.Get("orgID"); orgIDStr != "" {
orgID, err := influxdb.IDFromString(orgIDStr)
if err != nil {
return f, opts, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "orgID is invalid",
Err: err,
}
}
f.OrgID = orgID
} else if orgNameStr := q.Get("org"); orgNameStr != "" {
*f.Org = orgNameStr
}
return f, opts, err
}
2019-09-18 20:19:51 +00:00
type decodeStatus struct {
Status influxdb.Status `json:"status"`
}
func decodePostCheckRequest(ctx context.Context, r *http.Request) (influxdb.CheckCreate, error) {
var cc influxdb.CheckCreate
2019-07-25 10:30:25 +00:00
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(r.Body)
if err != nil {
2019-09-18 20:19:51 +00:00
return cc, &influxdb.Error{
2019-07-25 10:30:25 +00:00
Code: influxdb.EInvalid,
Err: err,
}
}
defer r.Body.Close()
chk, err := check.UnmarshalJSON(buf.Bytes())
if err != nil {
2019-09-18 20:19:51 +00:00
return cc, &influxdb.Error{
2019-07-25 10:30:25 +00:00
Code: influxdb.EInvalid,
Err: err,
}
}
2019-09-18 20:19:51 +00:00
var ds decodeStatus
err = json.Unmarshal(buf.Bytes(), &ds)
if err != nil {
return cc, &influxdb.Error{
Code: influxdb.EInvalid,
Err: err,
}
}
cc = influxdb.CheckCreate{Check: chk, Status: ds.Status}
return cc, nil
2019-07-25 10:30:25 +00:00
}
2019-09-18 20:19:51 +00:00
func decodePutCheckRequest(ctx context.Context, r *http.Request) (influxdb.CheckCreate, error) {
var cc influxdb.CheckCreate
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
2019-09-18 20:19:51 +00:00
return cc, &influxdb.Error{
2019-07-25 10:30:25 +00:00
Code: influxdb.EInvalid,
Msg: "url missing id",
2019-07-25 10:30:25 +00:00
}
}
i := new(influxdb.ID)
if err := i.DecodeFromString(id); err != nil {
2019-09-18 20:19:51 +00:00
return cc, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "invalid check id format",
}
}
2019-07-25 10:30:25 +00:00
defer r.Body.Close()
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(r.Body)
2019-07-25 10:30:25 +00:00
if err != nil {
2019-09-18 20:19:51 +00:00
return cc, &influxdb.Error{
2019-07-25 10:30:25 +00:00
Code: influxdb.EInvalid,
Msg: "unable to read HTTP body",
2019-07-25 10:30:25 +00:00
Err: err,
}
}
chk, err := check.UnmarshalJSON(buf.Bytes())
if err != nil {
2019-09-18 20:19:51 +00:00
return cc, &influxdb.Error{
2019-07-25 10:30:25 +00:00
Code: influxdb.EInvalid,
Msg: "malformed check body",
Err: err,
2019-07-25 10:30:25 +00:00
}
}
chk.SetID(*i)
if err := chk.Valid(); err != nil {
2019-09-18 20:19:51 +00:00
return cc, err
2019-07-25 10:30:25 +00:00
}
2019-09-18 20:19:51 +00:00
var ds decodeStatus
err = json.Unmarshal(buf.Bytes(), &ds)
if err != nil {
return cc, &influxdb.Error{
Code: influxdb.EInvalid,
Err: err,
}
}
cc = influxdb.CheckCreate{Check: chk, Status: ds.Status}
return cc, nil
2019-07-25 10:30:25 +00:00
}
type patchCheckRequest struct {
influxdb.ID
Update influxdb.CheckUpdate
}
func decodePatchCheckRequest(ctx context.Context, r *http.Request) (*patchCheckRequest, error) {
req := &patchCheckRequest{}
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "url missing id",
}
}
var i influxdb.ID
if err := i.DecodeFromString(id); err != nil {
return nil, err
}
req.ID = i
upd := &influxdb.CheckUpdate{}
if err := json.NewDecoder(r.Body).Decode(upd); err != nil {
return nil, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: err.Error(),
}
}
if err := upd.Valid(); err != nil {
return nil, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: err.Error(),
}
}
req.Update = *upd
return req, nil
}
// handlePostCheck is the HTTP handler for the POST /api/v2/checks route.
func (h *CheckHandler) handlePostCheck(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
h.Logger.Debug("check create request", zap.String("r", fmt.Sprint(r)))
chk, err := decodePostCheckRequest(ctx, r)
if err != nil {
h.Logger.Debug("failed to decode request", zap.Error(err))
h.HandleHTTPError(ctx, err, w)
return
}
auth, err := pctx.GetAuthorizer(ctx)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
if err := h.CheckService.CreateCheck(ctx, chk, auth.GetUserID()); err != nil {
2019-07-25 10:30:25 +00:00
h.HandleHTTPError(ctx, err, w)
return
}
h.Logger.Debug("check created", zap.String("check", fmt.Sprint(chk)))
2019-09-18 20:19:51 +00:00
cr, err := h.newCheckResponse(ctx, chk, []*influxdb.Label{})
if err != nil {
h.HandleHTTPError(ctx, err, w)
}
if err := encodeResponse(ctx, w, http.StatusCreated, cr); err != nil {
2019-07-25 10:30:25 +00:00
logEncodingError(h.Logger, r, err)
return
}
}
// handlePutCheck is the HTTP handler for the PUT /api/v2/checks route.
func (h *CheckHandler) handlePutCheck(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
h.Logger.Debug("check replace request", zap.String("r", fmt.Sprint(r)))
chk, err := decodePutCheckRequest(ctx, r)
if err != nil {
h.Logger.Debug("failed to decode request", zap.Error(err))
h.HandleHTTPError(ctx, err, w)
return
}
2019-09-18 20:19:51 +00:00
c, err := h.CheckService.UpdateCheck(ctx, chk.GetID(), chk)
2019-07-25 10:30:25 +00:00
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
2019-09-18 20:19:51 +00:00
labels, err := h.LabelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: c.GetID()})
2019-07-25 10:30:25 +00:00
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
2019-09-18 20:19:51 +00:00
h.Logger.Debug("check replaced", zap.String("check", fmt.Sprint(c)))
2019-07-25 10:30:25 +00:00
2019-09-18 20:19:51 +00:00
cr, err := h.newCheckResponse(ctx, c, labels)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, cr); err != nil {
2019-07-25 10:30:25 +00:00
logEncodingError(h.Logger, r, err)
return
}
}
// handlePatchCheck is the HTTP handler for the PATCH /api/v2/checks/:id route.
func (h *CheckHandler) handlePatchCheck(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
h.Logger.Debug("check patch request", zap.String("r", fmt.Sprint(r)))
req, err := decodePatchCheckRequest(ctx, r)
if err != nil {
h.Logger.Debug("failed to decode request", zap.Error(err))
h.HandleHTTPError(ctx, err, w)
return
}
chk, err := h.CheckService.PatchCheck(ctx, req.ID, req.Update)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
labels, err := h.LabelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: chk.GetID()})
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
h.Logger.Debug("check patch", zap.String("check", fmt.Sprint(chk)))
2019-09-18 20:19:51 +00:00
cr, err := h.newCheckResponse(ctx, chk, labels)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, cr); err != nil {
2019-07-25 10:30:25 +00:00
logEncodingError(h.Logger, r, err)
return
}
}
func (h *CheckHandler) handleDeleteCheck(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
h.Logger.Debug("check delete request", zap.String("r", fmt.Sprint(r)))
i, err := decodeGetCheckRequest(ctx, r)
if err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
if err = h.CheckService.DeleteCheck(ctx, i); err != nil {
h.HandleHTTPError(ctx, err, w)
return
}
h.Logger.Debug("check deleted", zap.String("checkID", fmt.Sprint(i)))
w.WriteHeader(http.StatusNoContent)
}