influxdb/http/label_service.go

395 lines
9.0 KiB
Go

package http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"path"
plat "github.com/influxdata/platform"
kerrors "github.com/influxdata/platform/kit/errors"
"github.com/julienschmidt/httprouter"
)
type LabelService struct {
Addr string
Token string
InsecureSkipVerify bool
BasePath string
}
type labelResponse struct {
Links map[string]string `json:"links"`
Label plat.Label `json:"label"`
}
// TODO: remove "dashboard" from this
func newLabelResponse(l *plat.Label) *labelResponse {
return &labelResponse{
Links: map[string]string{
"resource": fmt.Sprintf("/api/v2/%ss/%s", "dashboard", l.ResourceID),
},
Label: *l,
}
}
type labelsResponse struct {
Links map[string]string `json:"links"`
Labels []*plat.Label `json:"labels"`
}
func newLabelsResponse(opts plat.FindOptions, f plat.LabelFilter, ls []*plat.Label) *labelsResponse {
// TODO: Remove "dashboard" from this
return &labelsResponse{
Links: map[string]string{
"resource": fmt.Sprintf("/api/v2/%ss/%s", "dashboard", f.ResourceID),
},
Labels: ls,
}
}
// newGetLabelsHandler returns a handler func for a GET to /labels endpoints
func newGetLabelsHandler(s plat.LabelService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodeGetLabelsRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
opts := plat.FindOptions{}
labels, err := s.FindLabels(ctx, req.filter)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, newLabelsResponse(opts, req.filter, labels)); err != nil {
// TODO: this can potentially result in calling w.WriteHeader multiple times, we need to pass a logger in here
// some how. This isn't as simple as simply passing in a logger to this function since the time that this function
// is called is distinct from the time that a potential logger is set.
EncodeError(ctx, err, w)
return
}
}
}
type getLabelsRequest struct {
filter plat.LabelFilter
}
func decodeGetLabelsRequest(ctx context.Context, r *http.Request) (*getLabelsRequest, error) {
qp := r.URL.Query()
req := &getLabelsRequest{}
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, kerrors.InvalidDataf("url missing id")
}
var i plat.ID
if err := i.DecodeFromString(id); err != nil {
return nil, err
}
req.filter.ResourceID = i
if name := qp.Get("name"); name != "" {
req.filter.Name = name
}
return req, nil
}
// newPostLabelHandler returns a handler func for a POST to /labels endpoints
func newPostLabelHandler(s plat.LabelService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePostLabelRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := req.Label.Validate(); err != nil {
EncodeError(ctx, err, w)
return
}
if err := s.CreateLabel(ctx, &req.Label); err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusCreated, newLabelResponse(&req.Label)); err != nil {
// TODO: this can potentially result in calling w.WriteHeader multiple times, we need to pass a logger in here
// some how. This isn't as simple as simply passing in a logger to this function since the time that this function
// is called is distinct from the time that a potential logger is set.
EncodeError(ctx, err, w)
return
}
}
}
type postLabelRequest struct {
Label plat.Label
}
func decodePostLabelRequest(ctx context.Context, r *http.Request) (*postLabelRequest, error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, kerrors.InvalidDataf("url missing id")
}
var rid plat.ID
if err := rid.DecodeFromString(id); err != nil {
return nil, err
}
label := &plat.Label{}
if err := json.NewDecoder(r.Body).Decode(label); err != nil {
return nil, err
}
label.ResourceID = rid
if err := label.Validate(); err != nil {
return nil, err
}
req := &postLabelRequest{
Label: *label,
}
return req, nil
}
type patchLabelRequest struct {
label *plat.Label
upd plat.LabelUpdate
}
func decodePatchLabelRequest(ctx context.Context, r *http.Request) (*patchLabelRequest, error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, &plat.Error{
Code: plat.EInvalid,
Msg: "url missing resource id",
}
}
name := params.ByName("name")
if name == "" {
return nil, &plat.Error{
Code: plat.EInvalid,
Msg: "label name is missing",
}
}
var rid plat.ID
if err := rid.DecodeFromString(id); err != nil {
return nil, err
}
upd := &plat.LabelUpdate{}
if err := json.NewDecoder(r.Body).Decode(upd); err != nil {
return nil, err
}
return &patchLabelRequest{
label: &plat.Label{ResourceID: rid, Name: name},
upd: *upd,
}, nil
}
// newPatchLabelHandler returns a handler func for a PATCH to /labels endpoints
func newPatchLabelHandler(s plat.LabelService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePatchLabelRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
label, err := s.UpdateLabel(ctx, req.label, req.upd)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, newLabelResponse(label)); err != nil {
// TODO: this can potentially result in calling w.WriteHeader multiple times, we need to pass a logger in here
// some how. This isn't as simple as simply passing in a logger to this function since the time that this function
// is called is distinct from the time that a potential logger is set.
EncodeError(ctx, err, w)
return
}
}
}
// newDeleteLabelHandler returns a handler func for a DELETE to /labels endpoints
func newDeleteLabelHandler(s plat.LabelService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodeDeleteLabelRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
label := plat.Label{
ResourceID: req.ResourceID,
Name: req.Name,
}
if err := s.DeleteLabel(ctx, label); err != nil {
EncodeError(ctx, err, w)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
type deleteLabelRequest struct {
ResourceID plat.ID
Name string
}
func decodeDeleteLabelRequest(ctx context.Context, r *http.Request) (*deleteLabelRequest, error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, &plat.Error{
Code: plat.EInvalid,
Msg: "url missing resource id",
}
}
name := params.ByName("name")
if name == "" {
return nil, &plat.Error{
Code: plat.EInvalid,
Msg: "label name is missing",
}
}
var rid plat.ID
if err := rid.DecodeFromString(id); err != nil {
return nil, err
}
return &deleteLabelRequest{
Name: name,
ResourceID: rid,
}, nil
}
// FindLabels returns a slice of labels
func (s *LabelService) FindLabels(ctx context.Context, filter plat.LabelFilter, opt ...plat.FindOptions) ([]*plat.Label, error) {
url, err := newURL(s.Addr, resourceIDPath(s.BasePath, filter.ResourceID))
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return nil, err
}
SetToken(s.Token, req)
hc := newClient(url.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return nil, err
}
if err := CheckError(resp); err != nil {
return nil, err
}
var r labelsResponse
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
return nil, err
}
return r.Labels, nil
}
func (s *LabelService) CreateLabel(ctx context.Context, l *plat.Label) error {
if err := l.Validate(); err != nil {
return err
}
url, err := newURL(s.Addr, resourceIDPath(s.BasePath, l.ResourceID))
if err != nil {
return err
}
octets, err := json.Marshal(l)
if err != nil {
return err
}
req, err := http.NewRequest("POST", url.String(), bytes.NewReader(octets))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
SetToken(s.Token, req)
hc := newClient(url.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return err
}
if err := CheckError(resp); err != nil {
return err
}
if err := json.NewDecoder(resp.Body).Decode(l); err != nil {
return err
}
return nil
}
func (s *LabelService) DeleteLabel(ctx context.Context, l plat.Label) error {
url, err := newURL(s.Addr, labelNamePath(s.BasePath, l.ResourceID, l.Name))
if err != nil {
return err
}
req, err := http.NewRequest("DELETE", url.String(), nil)
if err != nil {
return err
}
SetToken(s.Token, req)
hc := newClient(url.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return err
}
return CheckError(resp)
}
func labelNamePath(basePath string, resourceID plat.ID, name string) string {
return path.Join(basePath, resourceID.String(), "labels", name)
}