2018-05-14 16:26:38 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2018-05-22 22:05:17 +00:00
|
|
|
"errors"
|
2018-05-14 16:26:38 +00:00
|
|
|
"net/http"
|
2018-05-16 18:59:35 +00:00
|
|
|
"path"
|
2018-05-14 16:26:38 +00:00
|
|
|
|
|
|
|
"github.com/influxdata/platform"
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors "github.com/influxdata/platform/kit/errors"
|
2018-05-14 16:26:38 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
)
|
|
|
|
|
|
|
|
// OrgHandler represents an HTTP API handler for orgs.
|
|
|
|
type OrgHandler struct {
|
|
|
|
*httprouter.Router
|
|
|
|
|
|
|
|
OrganizationService platform.OrganizationService
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewOrgHandler returns a new instance of OrgHandler.
|
|
|
|
func NewOrgHandler() *OrgHandler {
|
|
|
|
h := &OrgHandler{
|
|
|
|
Router: httprouter.New(),
|
|
|
|
}
|
|
|
|
|
|
|
|
h.HandlerFunc("POST", "/v1/orgs", h.handlePostOrg)
|
|
|
|
h.HandlerFunc("GET", "/v1/orgs", h.handleGetOrgs)
|
|
|
|
h.HandlerFunc("GET", "/v1/orgs/:id", h.handleGetOrg)
|
|
|
|
h.HandlerFunc("PATCH", "/v1/orgs/:id", h.handlePatchOrg)
|
2018-05-16 18:59:35 +00:00
|
|
|
h.HandlerFunc("DELETE", "/v1/orgs/:id", h.handleDeleteOrg)
|
2018-05-14 16:26:38 +00:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
|
|
|
// handlePostOrg is the HTTP handler for the POST /v1/orgs route.
|
|
|
|
func (h *OrgHandler) handlePostOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodePostOrgRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.OrganizationService.CreateOrganization(ctx, req.Org); err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, req.Org); err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type postOrgRequest struct {
|
|
|
|
Org *platform.Organization
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodePostOrgRequest(ctx context.Context, r *http.Request) (*postOrgRequest, error) {
|
|
|
|
o := &platform.Organization{}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(o); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &postOrgRequest{
|
|
|
|
Org: o,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetOrg is the HTTP handler for the GET /v1/orgs/:id route.
|
|
|
|
func (h *OrgHandler) handleGetOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetOrgRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := h.OrganizationService.FindOrganizationByID(ctx, req.OrgID)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, b); err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getOrgRequest struct {
|
|
|
|
OrgID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGetOrgRequest(ctx context.Context, r *http.Request) (*getOrgRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, kerrors.InvalidDataf("url missing id")
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
2018-05-16 18:59:35 +00:00
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
2018-05-14 16:26:38 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req := &getOrgRequest{
|
|
|
|
OrgID: i,
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetOrgs is the HTTP handler for the GET /v1/orgs route.
|
|
|
|
func (h *OrgHandler) handleGetOrgs(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetOrgsRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
orgs, _, err := h.OrganizationService.FindOrganizations(ctx, req.filter)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, orgs); err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getOrgsRequest struct {
|
|
|
|
filter platform.OrganizationFilter
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGetOrgsRequest(ctx context.Context, r *http.Request) (*getOrgsRequest, error) {
|
|
|
|
qp := r.URL.Query()
|
|
|
|
req := &getOrgsRequest{}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
if id := qp.Get("id"); id != "" {
|
2018-05-14 16:26:38 +00:00
|
|
|
req.filter.ID = &platform.ID{}
|
|
|
|
if err := req.filter.ID.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 17:50:34 +00:00
|
|
|
if name := qp.Get("name"); name != "" {
|
2018-05-14 16:26:38 +00:00
|
|
|
req.filter.Name = &name
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
// handleDeleteOrganization is the HTTP handler for the DELETE /v1/organizations/:id route.
|
|
|
|
func (h *OrgHandler) handleDeleteOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeDeleteOrganizationRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.OrganizationService.DeleteOrganization(ctx, req.OrganizationID); err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusAccepted)
|
|
|
|
}
|
|
|
|
|
|
|
|
type deleteOrganizationRequest struct {
|
|
|
|
OrganizationID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeDeleteOrganizationRequest(ctx context.Context, r *http.Request) (*deleteOrganizationRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, kerrors.InvalidDataf("url missing id")
|
2018-05-16 18:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req := &deleteOrganizationRequest{
|
|
|
|
OrganizationID: i,
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
// handlePatchOrg is the HTTP handler for the PATH /v1/orgs route.
|
|
|
|
func (h *OrgHandler) handlePatchOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodePatchOrgRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
o, err := h.OrganizationService.UpdateOrganization(ctx, req.OrgID, req.Update)
|
|
|
|
if err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, o); err != nil {
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type patchOrgRequest struct {
|
|
|
|
Update platform.OrganizationUpdate
|
|
|
|
OrgID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodePatchOrgRequest(ctx context.Context, r *http.Request) (*patchOrgRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, kerrors.InvalidDataf("url missing id")
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
2018-05-16 18:59:35 +00:00
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
2018-05-14 16:26:38 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var upd platform.OrganizationUpdate
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&upd); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &patchOrgRequest{
|
|
|
|
Update: upd,
|
|
|
|
OrgID: i,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
organizationPath = "/v1/orgs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// OrganizationService connects to Influx via HTTP using tokens to manage organizations.
|
|
|
|
type OrganizationService struct {
|
|
|
|
Addr string
|
|
|
|
Token string
|
|
|
|
InsecureSkipVerify bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OrganizationService) FindOrganizationByID(ctx context.Context, id platform.ID) (*platform.Organization, error) {
|
|
|
|
filter := platform.OrganizationFilter{ID: &id}
|
|
|
|
return s.FindOrganization(ctx, filter)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OrganizationService) FindOrganization(ctx context.Context, filter platform.OrganizationFilter) (*platform.Organization, error) {
|
|
|
|
os, n, err := s.FindOrganizations(ctx, filter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if n < 1 {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, errors.New("expected at least one organization")
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return os[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OrganizationService) FindOrganizations(ctx context.Context, filter platform.OrganizationFilter, opt ...platform.FindOptions) ([]*platform.Organization, int, error) {
|
|
|
|
url, err := newURL(s.Addr, organizationPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
qp := url.Query()
|
|
|
|
|
|
|
|
if filter.Name != nil {
|
2018-05-16 18:59:35 +00:00
|
|
|
qp.Add("name", *filter.Name)
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
if filter.ID != nil {
|
2018-05-16 18:59:35 +00:00
|
|
|
qp.Add("id", filter.ID.String())
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
url.RawQuery = qp.Encode()
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", url.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
hc := newClient(url.Scheme, s.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2018-05-23 18:29:01 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
|
|
|
return nil, 0, err
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var os []*platform.Organization
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&os); err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return os, len(os), nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateOrganization creates an organization.
|
|
|
|
func (s *OrganizationService) CreateOrganization(ctx context.Context, o *platform.Organization) error {
|
|
|
|
url, err := newURL(s.Addr, organizationPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
octets, err := json.Marshal(o)
|
|
|
|
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")
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
|
|
|
|
hc := newClient(url.Scheme, s.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-05-23 18:29:01 +00:00
|
|
|
// TODO(jsternberg): Should this check for a 201 explicitly?
|
|
|
|
if err := CheckError(resp); err != nil {
|
|
|
|
return err
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(o); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OrganizationService) UpdateOrganization(ctx context.Context, id platform.ID, upd platform.OrganizationUpdate) (*platform.Organization, error) {
|
2018-05-16 18:59:35 +00:00
|
|
|
u, err := newURL(s.Addr, organizationIDPath(id))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
octets, err := json.Marshal(upd)
|
|
|
|
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")
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-05-23 18:29:01 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
|
|
|
return nil, err
|
2018-05-16 18:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var o platform.Organization
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&o); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
return &o, nil
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OrganizationService) DeleteOrganization(ctx context.Context, id platform.ID) error {
|
2018-05-16 18:59:35 +00:00
|
|
|
u, err := newURL(s.Addr, organizationIDPath(id))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("DELETE", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-05-23 18:29:01 +00:00
|
|
|
return CheckError(resp)
|
2018-05-16 18:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func organizationIDPath(id platform.ID) string {
|
|
|
|
return path.Join(organizationPath, id.String())
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|