influxdb/http/telegraf.go

334 lines
10 KiB
Go

package http
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/golang/gddo/httputil"
"github.com/influxdata/platform"
pctx "github.com/influxdata/platform/context"
"github.com/influxdata/platform/kit/errors"
"github.com/julienschmidt/httprouter"
"go.uber.org/zap"
)
// TelegrafHandler is the handler for the telegraf service
type TelegrafHandler struct {
*httprouter.Router
Logger *zap.Logger
TelegrafService platform.TelegrafConfigStore
UserResourceMappingService platform.UserResourceMappingService
LabelService platform.LabelService
UserService platform.UserService
}
const (
telegrafsPath = "/api/v2/telegrafs"
telegrafsIDPath = "/api/v2/telegrafs/:id"
telegrafsIDMembersPath = "/api/v2/telegrafs/:id/members"
telegrafsIDMembersIDPath = "/api/v2/telegrafs/:id/members/:userID"
telegrafsIDOwnersPath = "/api/v2/telegrafs/:id/owners"
telegrafsIDOwnersIDPath = "/api/v2/telegrafs/:id/owners/:userID"
telegrafsIDLabelsPath = "/api/v2/telegrafs/:id/labels"
telegrafsIDLabelsNamePath = "/api/v2/telegrafs/:id/labels/:name"
)
// NewTelegrafHandler returns a new instance of TelegrafHandler.
func NewTelegrafHandler(
logger *zap.Logger,
mappingService platform.UserResourceMappingService,
labelService platform.LabelService,
telegrafSvc platform.TelegrafConfigStore,
userService platform.UserService,
) *TelegrafHandler {
h := &TelegrafHandler{
Router: NewRouter(),
UserResourceMappingService: mappingService,
LabelService: labelService,
TelegrafService: telegrafSvc,
Logger: logger,
UserService: userService,
}
h.HandlerFunc("POST", telegrafsPath, h.handlePostTelegraf)
h.HandlerFunc("GET", telegrafsPath, h.handleGetTelegrafs)
h.HandlerFunc("GET", telegrafsIDPath, h.handleGetTelegraf)
h.HandlerFunc("DELETE", telegrafsIDPath, h.handleDeleteTelegraf)
h.HandlerFunc("PUT", telegrafsIDPath, h.handlePutTelegraf)
h.HandlerFunc("POST", telegrafsIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafsResource, platform.Member))
h.HandlerFunc("GET", telegrafsIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafsResource, platform.Member))
h.HandlerFunc("DELETE", telegrafsIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member))
h.HandlerFunc("POST", telegrafsIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafsResource, platform.Owner))
h.HandlerFunc("GET", telegrafsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TelegrafsResource, platform.Owner))
h.HandlerFunc("DELETE", telegrafsIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner))
h.HandlerFunc("GET", telegrafsIDLabelsPath, newGetLabelsHandler(h.LabelService))
h.HandlerFunc("POST", telegrafsIDLabelsPath, newPostLabelHandler(h.LabelService))
h.HandlerFunc("DELETE", telegrafsIDLabelsNamePath, newDeleteLabelHandler(h.LabelService))
h.HandlerFunc("PATCH", telegrafsIDLabelsNamePath, newPatchLabelHandler(h.LabelService))
return h
}
type telegrafLinks struct {
Self string `json:"self"`
Labels string `json:"labels"`
}
type telegrafResponse struct {
*platform.TelegrafConfig
Labels []platform.Label `json:"labels"`
Links telegrafLinks `json:"links"`
}
type telegrafResponses struct {
TelegrafConfigs []telegrafResponse `json:"configurations"`
}
func newTelegrafResponse(tc *platform.TelegrafConfig, labels []*platform.Label) telegrafResponse {
res := telegrafResponse{
TelegrafConfig: tc,
Links: telegrafLinks{
Self: fmt.Sprintf("/api/v2/telegrafs/%s", tc.ID),
Labels: fmt.Sprintf("/api/v2/telegrafs/%s/labels", tc.ID),
},
Labels: []platform.Label{},
}
for _, l := range labels {
res.Labels = append(res.Labels, *l)
}
return res
}
func newTelegrafResponses(ctx context.Context, tcs []*platform.TelegrafConfig, labelService platform.LabelService) telegrafResponses {
resp := telegrafResponses{
TelegrafConfigs: make([]telegrafResponse, len(tcs)),
}
for i, c := range tcs {
labels, _ := labelService.FindLabels(ctx, platform.LabelFilter{ResourceID: c.ID})
resp.TelegrafConfigs[i] = newTelegrafResponse(c, labels)
}
return resp
}
func decodeGetTelegrafRequest(ctx context.Context, r *http.Request) (i platform.ID, err error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return i, errors.InvalidDataf("url missing id")
}
if err := i.DecodeFromString(id); err != nil {
return i, err
}
return i, nil
}
func (h *TelegrafHandler) handleGetTelegrafs(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
filter, err := decodeTelegrafConfigFilter(ctx, r)
if err != nil {
h.Logger.Debug("failed to decode request", zap.Error(err))
EncodeError(ctx, err, w)
return
}
tcs, _, err := h.TelegrafService.FindTelegrafConfigs(ctx, *filter)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, newTelegrafResponses(ctx, tcs, h.LabelService)); err != nil {
logEncodingError(h.Logger, r, err)
return
}
}
func (h *TelegrafHandler) handleGetTelegraf(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, err := decodeGetTelegrafRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
tc, err := h.TelegrafService.FindTelegrafConfigByID(ctx, id)
if err != nil {
EncodeError(ctx, err, w)
return
}
offers := []string{"application/toml", "application/json", "application/octet-stream"}
defaultOffer := "application/toml"
mimeType := httputil.NegotiateContentType(r, offers, defaultOffer)
switch mimeType {
case "application/octet-stream":
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.toml\"", strings.Replace(strings.TrimSpace(tc.Name), " ", "_", -1)))
w.WriteHeader(http.StatusOK)
w.Write([]byte(tc.TOML()))
case "application/json":
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: tc.ID})
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, newTelegrafResponse(tc, labels)); err != nil {
logEncodingError(h.Logger, r, err)
return
}
case "application/toml":
w.Header().Set("Content-Type", "application/toml; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(tc.TOML()))
}
}
func decodeTelegrafConfigFilter(ctx context.Context, r *http.Request) (*platform.TelegrafConfigFilter, error) {
f := &platform.TelegrafConfigFilter{}
urm, err := decodeUserResourceMappingFilter(ctx, r)
if err != nil {
f.UserResourceMappingFilter = *urm
}
q := r.URL.Query()
if orgIDStr := q.Get("orgID"); orgIDStr != "" {
orgID, _ := platform.IDFromString(orgIDStr)
f.OrganizationID = orgID
}
return f, err
}
func decodeUserResourceMappingFilter(ctx context.Context, r *http.Request) (*platform.UserResourceMappingFilter, error) {
q := r.URL.Query()
f := &platform.UserResourceMappingFilter{
Resource: platform.TelegrafsResource,
}
if idStr := q.Get("resourceId"); idStr != "" {
id, err := platform.IDFromString(idStr)
if err != nil {
return nil, err
}
f.ResourceID = *id
}
if idStr := q.Get("userId"); idStr != "" {
id, err := platform.IDFromString(idStr)
if err != nil {
return nil, err
}
f.UserID = *id
}
return f, nil
}
func decodePostTelegrafRequest(ctx context.Context, r *http.Request) (*platform.TelegrafConfig, error) {
tc := new(platform.TelegrafConfig)
err := json.NewDecoder(r.Body).Decode(tc)
return tc, err
}
func decodePutTelegrafRequest(ctx context.Context, r *http.Request) (*platform.TelegrafConfig, error) {
tc := new(platform.TelegrafConfig)
if err := json.NewDecoder(r.Body).Decode(tc); err != nil {
return nil, err
}
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, errors.InvalidDataf("url missing id")
}
i := new(platform.ID)
if err := i.DecodeFromString(id); err != nil {
return nil, err
}
tc.ID = *i
return tc, nil
}
// handlePostTelegraf is the HTTP handler for the POST /api/v2/telegrafs route.
func (h *TelegrafHandler) handlePostTelegraf(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tc, err := decodePostTelegrafRequest(ctx, r)
if err != nil {
h.Logger.Debug("failed to decode request", zap.Error(err))
EncodeError(ctx, err, w)
return
}
auth, err := pctx.GetAuthorizer(ctx)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := h.TelegrafService.CreateTelegrafConfig(ctx, tc, auth.GetUserID()); err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusCreated, newTelegrafResponse(tc, []*platform.Label{})); err != nil {
logEncodingError(h.Logger, r, err)
return
}
}
// handlePutTelegraf is the HTTP handler for the POST /api/v2/telegrafs route.
func (h *TelegrafHandler) handlePutTelegraf(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tc, err := decodePutTelegrafRequest(ctx, r)
if err != nil {
h.Logger.Debug("failed to decode request", zap.Error(err))
EncodeError(ctx, err, w)
return
}
auth, err := pctx.GetAuthorizer(ctx)
if err != nil {
EncodeError(ctx, err, w)
return
}
tc, err = h.TelegrafService.UpdateTelegrafConfig(ctx, tc.ID, tc, auth.GetUserID())
if err != nil {
EncodeError(ctx, err, w)
return
}
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: tc.ID})
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, newTelegrafResponse(tc, labels)); err != nil {
logEncodingError(h.Logger, r, err)
return
}
}
func (h *TelegrafHandler) handleDeleteTelegraf(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
i, err := decodeGetTelegrafRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err = h.TelegrafService.DeleteTelegrafConfig(ctx, i); err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusNoContent, nil); err != nil {
logEncodingError(h.Logger, r, err)
return
}
}