influxdb/http/macro_service.go

529 lines
11 KiB
Go

package http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"path"
"github.com/influxdata/platform"
kerrors "github.com/influxdata/platform/kit/errors"
"github.com/julienschmidt/httprouter"
)
const (
macroPath = "/api/v2/macros"
)
// MacroHandler is the handler for the macro service
type MacroHandler struct {
*httprouter.Router
MacroService platform.MacroService
}
// NewMacroHandler creates a new MacroHandler
func NewMacroHandler() *MacroHandler {
h := &MacroHandler{
Router: httprouter.New(),
}
h.HandlerFunc("GET", "/api/v2/macros", h.handleGetMacros)
h.HandlerFunc("POST", "/api/v2/macros", h.handlePostMacro)
h.HandlerFunc("GET", "/api/v2/macros/:id", h.handleGetMacro)
h.HandlerFunc("PATCH", "/api/v2/macros/:id", h.handlePatchMacro)
h.HandlerFunc("PUT", "/api/v2/macros/:id", h.handlePutMacro)
h.HandlerFunc("DELETE", "/api/v2/macros/:id", h.handleDeleteMacro)
return h
}
type macrosLinks struct {
Self string `json:"self"`
}
type getMacrosResponse struct {
Macros []macroResponse `json:"macros"`
Links macrosLinks `json:"links"`
}
func (r getMacrosResponse) ToPlatform() []*platform.Macro {
macros := make([]*platform.Macro, len(r.Macros))
for i := range r.Macros {
macros[i] = r.Macros[i].Macro
}
return macros
}
func newGetMacrosResponse(macros []*platform.Macro) getMacrosResponse {
resp := getMacrosResponse{
Macros: make([]macroResponse, 0, len(macros)),
Links: macrosLinks{
Self: "/api/v2/macros",
},
}
for _, macro := range macros {
resp.Macros = append(resp.Macros, newMacroResponse(macro))
}
return resp
}
func (h *MacroHandler) handleGetMacros(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
macros, err := h.MacroService.FindMacros(ctx)
if err != nil {
EncodeError(ctx, kerrors.InternalErrorf("could not read macros: %v", err), w)
return
}
err = encodeResponse(ctx, w, http.StatusOK, newGetMacrosResponse(macros))
if err != nil {
EncodeError(ctx, err, w)
return
}
}
func requestMacroID(ctx context.Context) (platform.ID, error) {
params := httprouter.ParamsFromContext(ctx)
urlID := params.ByName("id")
if urlID == "" {
return platform.InvalidID(), kerrors.InvalidDataf("url missing id")
}
id, err := platform.IDFromString(urlID)
return *id, err
}
func (h *MacroHandler) handleGetMacro(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, err := requestMacroID(ctx)
if err != nil {
EncodeError(ctx, err, w)
return
}
macro, err := h.MacroService.FindMacroByID(ctx, id)
if err != nil {
EncodeError(ctx, err, w)
return
}
err = encodeResponse(ctx, w, http.StatusOK, newMacroResponse(macro))
if err != nil {
EncodeError(ctx, err, w)
return
}
}
type macroLinks struct {
Self string `json:"self"`
}
type macroResponse struct {
*platform.Macro
Links macroLinks `json:"links"`
}
func newMacroResponse(m *platform.Macro) macroResponse {
return macroResponse{
Macro: m,
Links: macroLinks{
Self: fmt.Sprintf("/api/v2/macros/%s", m.ID),
},
}
}
func (h *MacroHandler) handlePostMacro(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePostMacroRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
err = h.MacroService.CreateMacro(ctx, req.macro)
if err != nil {
EncodeError(ctx, err, w)
return
}
err = encodeResponse(ctx, w, http.StatusCreated, newMacroResponse(req.macro))
if err != nil {
EncodeError(ctx, err, w)
return
}
}
type postMacroRequest struct {
macro *platform.Macro
}
func (r *postMacroRequest) Valid() error {
return r.macro.Valid()
}
func decodePostMacroRequest(ctx context.Context, r *http.Request) (*postMacroRequest, error) {
m := &platform.Macro{}
err := json.NewDecoder(r.Body).Decode(m)
if err != nil {
return nil, kerrors.MalformedDataf(err.Error())
}
req := &postMacroRequest{
macro: m,
}
if err := req.Valid(); err != nil {
return nil, kerrors.InvalidDataf(err.Error())
}
return req, nil
}
func (h *MacroHandler) handlePatchMacro(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePatchMacroRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
macro, err := h.MacroService.UpdateMacro(ctx, req.id, req.macroUpdate)
if err != nil {
EncodeError(ctx, err, w)
return
}
err = encodeResponse(ctx, w, http.StatusOK, newMacroResponse(macro))
if err != nil {
EncodeError(ctx, err, w)
return
}
}
type patchMacroRequest struct {
id platform.ID
macroUpdate *platform.MacroUpdate
}
func (r *patchMacroRequest) Valid() error {
return r.macroUpdate.Valid()
}
func decodePatchMacroRequest(ctx context.Context, r *http.Request) (*patchMacroRequest, error) {
u := &platform.MacroUpdate{}
err := json.NewDecoder(r.Body).Decode(u)
if err != nil {
return nil, kerrors.MalformedDataf(err.Error())
}
id, err := requestMacroID(ctx)
if err != nil {
return nil, err
}
req := &patchMacroRequest{
id: id,
macroUpdate: u,
}
if err := req.Valid(); err != nil {
return nil, kerrors.InvalidDataf(err.Error())
}
return req, nil
}
func (h *MacroHandler) handlePutMacro(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePutMacroRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
err = h.MacroService.ReplaceMacro(ctx, req.macro)
if err != nil {
EncodeError(ctx, err, w)
return
}
err = encodeResponse(ctx, w, http.StatusOK, newMacroResponse(req.macro))
if err != nil {
EncodeError(ctx, err, w)
return
}
}
type putMacroRequest struct {
macro *platform.Macro
}
func (r *putMacroRequest) Valid() error {
return r.macro.Valid()
}
func decodePutMacroRequest(ctx context.Context, r *http.Request) (*putMacroRequest, error) {
m := &platform.Macro{}
err := json.NewDecoder(r.Body).Decode(m)
if err != nil {
return nil, kerrors.MalformedDataf(err.Error())
}
req := &putMacroRequest{
macro: m,
}
if err := req.Valid(); err != nil {
return nil, kerrors.InvalidDataf(err.Error())
}
return req, nil
}
func (h *MacroHandler) handleDeleteMacro(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id, err := requestMacroID(ctx)
if err != nil {
EncodeError(ctx, err, w)
return
}
err = h.MacroService.DeleteMacro(ctx, id)
if err != nil {
EncodeError(ctx, err, w)
return
}
w.WriteHeader(http.StatusNoContent)
}
// MacroService is a macro service over HTTP to the influxdb server
type MacroService struct {
Addr string
Token string
InsecureSkipVerify bool
}
// FindMacroByID finds a single macro from the store by its ID
func (s *MacroService) FindMacroByID(ctx context.Context, id platform.ID) (*platform.Macro, error) {
path := macroIDPath(id)
url, err := newURL(s.Addr, path)
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 mr macroResponse
if err := json.NewDecoder(resp.Body).Decode(&mr); err != nil {
return nil, err
}
macro := mr.Macro
return macro, nil
}
// FindMacros returns all macros in the store
func (s *MacroService) FindMacros(ctx context.Context) ([]*platform.Macro, error) {
url, err := newURL(s.Addr, macroPath)
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 ms getMacrosResponse
if err := json.NewDecoder(resp.Body).Decode(&ms); err != nil {
return nil, err
}
macros := ms.ToPlatform()
return macros, nil
}
// CreateMacro creates a new macro and assigns it an platform.ID
func (s *MacroService) CreateMacro(ctx context.Context, m *platform.Macro) error {
if err := m.Valid(); err != nil {
return kerrors.InvalidDataf(err.Error())
}
url, err := newURL(s.Addr, macroPath)
if err != nil {
return err
}
octets, err := json.Marshal(m)
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
}
return json.NewDecoder(resp.Body).Decode(m)
}
// UpdateMacro updates a single macro with a changeset
func (s *MacroService) UpdateMacro(ctx context.Context, id platform.ID, update *platform.MacroUpdate) (*platform.Macro, error) {
u, err := newURL(s.Addr, macroIDPath(id))
if err != nil {
return nil, err
}
octets, err := json.Marshal(update)
if err != nil {
return nil, err
}
req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(octets))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
SetToken(s.Token, req)
hc := newClient(u.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return nil, err
}
if err := CheckError(resp); err != nil {
return nil, err
}
var m platform.Macro
if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
return nil, err
}
defer resp.Body.Close()
return &m, nil
}
// ReplaceMacro replaces a single macro
func (s *MacroService) ReplaceMacro(ctx context.Context, macro *platform.Macro) error {
u, err := newURL(s.Addr, macroIDPath(macro.ID))
if err != nil {
return err
}
octets, err := json.Marshal(macro)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(octets))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
SetToken(s.Token, req)
hc := newClient(u.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(&macro); err != nil {
return err
}
defer resp.Body.Close()
return nil
}
// DeleteMacro removes a macro from the store
func (s *MacroService) DeleteMacro(ctx context.Context, id platform.ID) error {
u, err := newURL(s.Addr, macroIDPath(id))
if err != nil {
return err
}
req, err := http.NewRequest("DELETE", u.String(), nil)
if err != nil {
return err
}
SetToken(s.Token, req)
hc := newClient(u.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return err
}
return CheckError(resp)
}
func macroIDPath(id platform.ID) string {
return path.Join(macroPath, id.String())
}