2018-05-14 16:26:38 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2018-09-06 16:19:58 +00:00
|
|
|
"fmt"
|
2019-01-08 00:37:16 +00:00
|
|
|
platform "github.com/influxdata/influxdb"
|
2018-05-14 16:26:38 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
2018-12-20 16:07:46 +00:00
|
|
|
"go.uber.org/zap"
|
2019-02-01 03:10:05 +00:00
|
|
|
"net/http"
|
|
|
|
"path"
|
2018-05-14 16:26:38 +00:00
|
|
|
)
|
|
|
|
|
2019-01-16 03:34:09 +00:00
|
|
|
// OrgBackend is all services and associated parameters required to construct
|
|
|
|
// the OrgHandler.
|
|
|
|
type OrgBackend struct {
|
|
|
|
Logger *zap.Logger
|
|
|
|
|
|
|
|
OrganizationService platform.OrganizationService
|
|
|
|
OrganizationOperationLogService platform.OrganizationOperationLogService
|
|
|
|
UserResourceMappingService platform.UserResourceMappingService
|
|
|
|
SecretService platform.SecretService
|
|
|
|
LabelService platform.LabelService
|
|
|
|
UserService platform.UserService
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewOrgBackend(b *APIBackend) *OrgBackend {
|
|
|
|
return &OrgBackend{
|
|
|
|
Logger: b.Logger.With(zap.String("handler", "org")),
|
|
|
|
|
|
|
|
OrganizationService: b.OrganizationService,
|
|
|
|
OrganizationOperationLogService: b.OrganizationOperationLogService,
|
|
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
|
|
SecretService: b.SecretService,
|
|
|
|
LabelService: b.LabelService,
|
|
|
|
UserService: b.UserService,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
// OrgHandler represents an HTTP API handler for orgs.
|
|
|
|
type OrgHandler struct {
|
|
|
|
*httprouter.Router
|
|
|
|
|
2018-12-20 16:07:46 +00:00
|
|
|
Logger *zap.Logger
|
|
|
|
|
2018-11-02 18:21:14 +00:00
|
|
|
OrganizationService platform.OrganizationService
|
|
|
|
OrganizationOperationLogService platform.OrganizationOperationLogService
|
|
|
|
UserResourceMappingService platform.UserResourceMappingService
|
2018-11-16 16:45:00 +00:00
|
|
|
SecretService platform.SecretService
|
2018-12-11 18:15:45 +00:00
|
|
|
LabelService platform.LabelService
|
2018-11-17 15:54:21 +00:00
|
|
|
UserService platform.UserService
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-27 22:50:32 +00:00
|
|
|
const (
|
|
|
|
organizationsPath = "/api/v2/orgs"
|
|
|
|
organizationsIDPath = "/api/v2/orgs/:id"
|
2018-11-02 18:21:14 +00:00
|
|
|
organizationsIDLogPath = "/api/v2/orgs/:id/log"
|
2018-09-27 22:50:32 +00:00
|
|
|
organizationsIDMembersPath = "/api/v2/orgs/:id/members"
|
2018-11-14 10:54:06 +00:00
|
|
|
organizationsIDMembersIDPath = "/api/v2/orgs/:id/members/:userID"
|
2018-09-27 22:50:32 +00:00
|
|
|
organizationsIDOwnersPath = "/api/v2/orgs/:id/owners"
|
2018-11-14 10:54:06 +00:00
|
|
|
organizationsIDOwnersIDPath = "/api/v2/orgs/:id/owners/:userID"
|
2018-11-16 16:45:00 +00:00
|
|
|
organizationsIDSecretsPath = "/api/v2/orgs/:id/secrets"
|
|
|
|
// TODO(desa): need a way to specify which secrets to delete. this should work for now
|
|
|
|
organizationsIDSecretsDeletePath = "/api/v2/orgs/:id/secrets/delete"
|
2018-12-11 18:15:45 +00:00
|
|
|
organizationsIDLabelsPath = "/api/v2/orgs/:id/labels"
|
2019-01-18 19:03:36 +00:00
|
|
|
organizationsIDLabelsIDPath = "/api/v2/orgs/:id/labels/:lid"
|
2018-09-27 22:50:32 +00:00
|
|
|
)
|
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
// NewOrgHandler returns a new instance of OrgHandler.
|
2019-01-16 03:34:09 +00:00
|
|
|
func NewOrgHandler(b *OrgBackend) *OrgHandler {
|
2018-05-14 16:26:38 +00:00
|
|
|
h := &OrgHandler{
|
2019-01-08 00:37:16 +00:00
|
|
|
Router: NewRouter(),
|
|
|
|
Logger: zap.NewNop(),
|
|
|
|
|
2019-01-16 03:34:09 +00:00
|
|
|
OrganizationService: b.OrganizationService,
|
|
|
|
OrganizationOperationLogService: b.OrganizationOperationLogService,
|
|
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
|
|
SecretService: b.SecretService,
|
|
|
|
LabelService: b.LabelService,
|
|
|
|
UserService: b.UserService,
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-27 22:50:32 +00:00
|
|
|
h.HandlerFunc("POST", organizationsPath, h.handlePostOrg)
|
|
|
|
h.HandlerFunc("GET", organizationsPath, h.handleGetOrgs)
|
|
|
|
h.HandlerFunc("GET", organizationsIDPath, h.handleGetOrg)
|
2018-11-02 18:21:14 +00:00
|
|
|
h.HandlerFunc("GET", organizationsIDLogPath, h.handleGetOrgLog)
|
2018-09-27 22:50:32 +00:00
|
|
|
h.HandlerFunc("PATCH", organizationsIDPath, h.handlePatchOrg)
|
|
|
|
h.HandlerFunc("DELETE", organizationsIDPath, h.handleDeleteOrg)
|
|
|
|
|
2019-01-17 03:35:54 +00:00
|
|
|
memberBackend := MemberBackend{
|
|
|
|
Logger: b.Logger.With(zap.String("handler", "member")),
|
|
|
|
ResourceType: platform.OrgsResourceType,
|
|
|
|
UserType: platform.Member,
|
|
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
|
|
UserService: b.UserService,
|
|
|
|
}
|
|
|
|
h.HandlerFunc("POST", organizationsIDMembersPath, newPostMemberHandler(memberBackend))
|
|
|
|
h.HandlerFunc("GET", organizationsIDMembersPath, newGetMembersHandler(memberBackend))
|
|
|
|
h.HandlerFunc("DELETE", organizationsIDMembersIDPath, newDeleteMemberHandler(memberBackend))
|
|
|
|
|
|
|
|
ownerBackend := MemberBackend{
|
|
|
|
Logger: b.Logger.With(zap.String("handler", "member")),
|
|
|
|
ResourceType: platform.OrgsResourceType,
|
|
|
|
UserType: platform.Owner,
|
|
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
|
|
UserService: b.UserService,
|
|
|
|
}
|
|
|
|
h.HandlerFunc("POST", organizationsIDOwnersPath, newPostMemberHandler(ownerBackend))
|
|
|
|
h.HandlerFunc("GET", organizationsIDOwnersPath, newGetMembersHandler(ownerBackend))
|
|
|
|
h.HandlerFunc("DELETE", organizationsIDOwnersIDPath, newDeleteMemberHandler(ownerBackend))
|
2018-09-27 22:50:32 +00:00
|
|
|
|
2018-11-16 16:45:00 +00:00
|
|
|
h.HandlerFunc("GET", organizationsIDSecretsPath, h.handleGetSecrets)
|
|
|
|
h.HandlerFunc("PATCH", organizationsIDSecretsPath, h.handlePatchSecrets)
|
|
|
|
// TODO(desa): need a way to specify which secrets to delete. this should work for now
|
|
|
|
h.HandlerFunc("POST", organizationsIDSecretsDeletePath, h.handleDeleteSecrets)
|
|
|
|
|
2018-12-11 18:15:45 +00:00
|
|
|
h.HandlerFunc("GET", organizationsIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
|
|
|
h.HandlerFunc("POST", organizationsIDLabelsPath, newPostLabelHandler(h.LabelService))
|
2019-01-18 19:03:36 +00:00
|
|
|
h.HandlerFunc("DELETE", organizationsIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
2018-12-11 18:15:45 +00:00
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2018-09-11 07:32:43 +00:00
|
|
|
type orgsResponse struct {
|
|
|
|
Links map[string]string `json:"links"`
|
|
|
|
Organizations []*orgResponse `json:"orgs"`
|
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
func (o orgsResponse) ToPlatform() []*platform.Organization {
|
|
|
|
orgs := make([]*platform.Organization, len(o.Organizations))
|
|
|
|
for i := range o.Organizations {
|
|
|
|
orgs[i] = &o.Organizations[i].Organization
|
|
|
|
}
|
|
|
|
return orgs
|
|
|
|
}
|
|
|
|
|
2018-09-11 07:32:43 +00:00
|
|
|
func newOrgsResponse(orgs []*platform.Organization) *orgsResponse {
|
|
|
|
res := orgsResponse{
|
|
|
|
Links: map[string]string{
|
2018-09-26 08:49:19 +00:00
|
|
|
"self": "/api/v2/orgs",
|
2018-09-11 07:32:43 +00:00
|
|
|
},
|
2018-09-17 03:24:48 +00:00
|
|
|
Organizations: []*orgResponse{},
|
2018-09-11 07:32:43 +00:00
|
|
|
}
|
|
|
|
for _, org := range orgs {
|
|
|
|
res.Organizations = append(res.Organizations, newOrgResponse(org))
|
|
|
|
}
|
|
|
|
return &res
|
|
|
|
}
|
|
|
|
|
|
|
|
type orgResponse struct {
|
|
|
|
Links map[string]string `json:"links"`
|
|
|
|
platform.Organization
|
|
|
|
}
|
|
|
|
|
|
|
|
func newOrgResponse(o *platform.Organization) *orgResponse {
|
|
|
|
return &orgResponse{
|
|
|
|
Links: map[string]string{
|
2018-09-26 08:49:19 +00:00
|
|
|
"self": fmt.Sprintf("/api/v2/orgs/%s", o.ID),
|
2018-11-02 18:21:14 +00:00
|
|
|
"log": fmt.Sprintf("/api/v2/orgs/%s/log", o.ID),
|
2018-09-26 08:49:19 +00:00
|
|
|
"members": fmt.Sprintf("/api/v2/orgs/%s/members", o.ID),
|
2018-12-27 18:51:31 +00:00
|
|
|
"secrets": fmt.Sprintf("/api/v2/orgs/%s/secrets", o.ID),
|
|
|
|
"labels": fmt.Sprintf("/api/v2/orgs/%s/labels", o.ID),
|
2018-09-26 08:49:19 +00:00
|
|
|
"buckets": fmt.Sprintf("/api/v2/buckets?org=%s", o.Name),
|
|
|
|
"tasks": fmt.Sprintf("/api/v2/tasks?org=%s", o.Name),
|
|
|
|
"dashboards": fmt.Sprintf("/api/v2/dashboards?org=%s", o.Name),
|
2018-09-11 07:32:43 +00:00
|
|
|
},
|
|
|
|
Organization: *o,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-16 16:45:00 +00:00
|
|
|
type secretsResponse struct {
|
|
|
|
Links map[string]string `json:"links"`
|
|
|
|
Secrets []string `json:"secrets"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newSecretsResponse(orgID platform.ID, ks []string) *secretsResponse {
|
|
|
|
return &secretsResponse{
|
|
|
|
Links: map[string]string{
|
|
|
|
"org": fmt.Sprintf("/api/v2/orgs/%s", orgID),
|
|
|
|
"secrets": fmt.Sprintf("/api/v2/orgs/%s/secrets", orgID),
|
|
|
|
},
|
|
|
|
Secrets: ks,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-26 08:49:19 +00:00
|
|
|
// handlePostOrg is the HTTP handler for the POST /api/v2/orgs route.
|
2018-05-14 16:26:38 +00:00
|
|
|
func (h *OrgHandler) handlePostOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodePostOrgRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.OrganizationService.CreateOrganization(ctx, req.Org); err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-11 07:32:43 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, newOrgResponse(req.Org)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
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
|
|
|
|
}
|
|
|
|
|
2018-09-26 08:49:19 +00:00
|
|
|
// handleGetOrg is the HTTP handler for the GET /api/v2/orgs/:id route.
|
2018-05-14 16:26:38 +00:00
|
|
|
func (h *OrgHandler) handleGetOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetOrgRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := h.OrganizationService.FindOrganizationByID(ctx, req.OrgID)
|
|
|
|
if err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-11 07:32:43 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newOrgResponse(b)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
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 == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "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
|
|
|
|
}
|
|
|
|
|
2018-09-26 08:49:19 +00:00
|
|
|
// handleGetOrgs is the HTTP handler for the GET /api/v2/orgs route.
|
2018-05-14 16:26:38 +00:00
|
|
|
func (h *OrgHandler) handleGetOrgs(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetOrgsRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
orgs, _, err := h.OrganizationService.FindOrganizations(ctx, req.filter)
|
|
|
|
if err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-11 07:32:43 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newOrgsResponse(orgs)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
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-10-10 19:18:29 +00:00
|
|
|
if orgID := qp.Get("id"); orgID != "" {
|
|
|
|
id, err := platform.IDFromString(orgID)
|
2018-07-20 10:24:07 +00:00
|
|
|
if err != nil {
|
2018-05-14 16:26:38 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-10 19:18:29 +00:00
|
|
|
req.filter.ID = id
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
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-09-26 08:49:19 +00:00
|
|
|
// handleDeleteOrganization is the HTTP handler for the DELETE /api/v2/orgs/:id route.
|
2018-05-16 18:59:35 +00:00
|
|
|
func (h *OrgHandler) handleDeleteOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeDeleteOrganizationRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.OrganizationService.DeleteOrganization(ctx, req.OrganizationID); err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
w.WriteHeader(http.StatusNoContent)
|
2018-05-16 18:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "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-09-26 08:49:19 +00:00
|
|
|
// handlePatchOrg is the HTTP handler for the PATH /api/v2/orgs route.
|
2018-05-14 16:26:38 +00:00
|
|
|
func (h *OrgHandler) handlePatchOrg(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodePatchOrgRequest(ctx, r)
|
|
|
|
if err != nil {
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(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-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-11 07:32:43 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newOrgResponse(o)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
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 == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "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
|
|
|
|
}
|
|
|
|
|
2018-11-16 16:45:00 +00:00
|
|
|
// handleGetSecrets is the HTTP handler for the GET /api/v2/orgs/:id/secrets route.
|
|
|
|
func (h *OrgHandler) handleGetSecrets(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetSecretsRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ks, err := h.SecretService.GetSecretKeys(ctx, req.orgID)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newSecretsResponse(req.orgID, ks)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
2018-11-16 16:45:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getSecretsRequest struct {
|
|
|
|
orgID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGetSecretsRequest(ctx context.Context, r *http.Request) (*getSecretsRequest, error) {
|
|
|
|
req := &getSecretsRequest{}
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "url missing id",
|
|
|
|
}
|
2018-11-16 16:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.orgID = i
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetPatchSecrets is the HTTP handler for the PATCH /api/v2/orgs/:id/secrets route.
|
|
|
|
func (h *OrgHandler) handlePatchSecrets(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodePatchSecretsRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.SecretService.PatchSecrets(ctx, req.orgID, req.secrets); err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
|
|
|
type patchSecretsRequest struct {
|
|
|
|
orgID platform.ID
|
|
|
|
secrets map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodePatchSecretsRequest(ctx context.Context, r *http.Request) (*patchSecretsRequest, error) {
|
|
|
|
req := &patchSecretsRequest{}
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "url missing id",
|
|
|
|
}
|
2018-11-16 16:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.orgID = i
|
|
|
|
req.secrets = map[string]string{}
|
|
|
|
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req.secrets); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleDeleteSecrets is the HTTP handler for the DELETE /api/v2/orgs/:id/secrets route.
|
|
|
|
func (h *OrgHandler) handleDeleteSecrets(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeDeleteSecretsRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.SecretService.DeleteSecret(ctx, req.orgID, req.secrets...); err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
|
|
|
type deleteSecretsRequest struct {
|
|
|
|
orgID platform.ID
|
|
|
|
secrets []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeDeleteSecretsRequest(ctx context.Context, r *http.Request) (*deleteSecretsRequest, error) {
|
|
|
|
req := &deleteSecretsRequest{}
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "url missing id",
|
|
|
|
}
|
2018-11-16 16:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.orgID = i
|
|
|
|
req.secrets = []string{}
|
|
|
|
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req.secrets); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
const (
|
2018-09-26 08:49:19 +00:00
|
|
|
organizationPath = "/api/v2/orgs"
|
2018-05-14 16:26:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// OrganizationService connects to Influx via HTTP using tokens to manage organizations.
|
|
|
|
type OrganizationService struct {
|
|
|
|
Addr string
|
|
|
|
Token string
|
|
|
|
InsecureSkipVerify bool
|
2018-12-05 14:57:26 +00:00
|
|
|
// OpPrefix is for not found errors.
|
|
|
|
OpPrefix string
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
// FindOrganizationByID gets a single organization with a given id using HTTP.
|
2018-05-14 16:26:38 +00:00
|
|
|
func (s *OrganizationService) FindOrganizationByID(ctx context.Context, id platform.ID) (*platform.Organization, error) {
|
|
|
|
filter := platform.OrganizationFilter{ID: &id}
|
2018-12-05 14:57:26 +00:00
|
|
|
o, err := s.FindOrganization(ctx, filter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Op: s.OpPrefix + platform.OpFindOrganizationByID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return o, nil
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
// FindOrganization gets a single organization matching the filter using HTTP.
|
2018-05-14 16:26:38 +00:00
|
|
|
func (s *OrganizationService) FindOrganization(ctx context.Context, filter platform.OrganizationFilter) (*platform.Organization, error) {
|
|
|
|
os, n, err := s.FindOrganizations(ctx, filter)
|
|
|
|
if err != nil {
|
2018-12-05 14:57:26 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Op: s.OpPrefix + platform.OpFindOrganization,
|
|
|
|
}
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-05 22:53:57 +00:00
|
|
|
if n == 0 {
|
2018-12-05 14:57:26 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.ENotFound,
|
|
|
|
Op: s.OpPrefix + platform.OpFindOrganization,
|
|
|
|
Msg: "organization not found",
|
|
|
|
}
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return os[0], nil
|
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
// FindOrganizations returns all organizations that match the filter via HTTP.
|
2018-05-14 16:26:38 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-08-27 19:18:11 +00:00
|
|
|
SetToken(s.Token, req)
|
2018-05-14 16:26:38 +00:00
|
|
|
hc := newClient(url.Scheme, s.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
2019-01-18 22:32:53 +00:00
|
|
|
defer resp.Body.Close()
|
2018-05-14 16:26:38 +00:00
|
|
|
|
2019-01-24 01:02:37 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
2018-05-23 18:29:01 +00:00
|
|
|
return nil, 0, err
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
var os orgsResponse
|
2018-05-14 16:26:38 +00:00
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&os); err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
orgs := os.ToPlatform()
|
|
|
|
return orgs, len(orgs), nil
|
2018-05-14 16:26:38 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateOrganization creates an organization.
|
|
|
|
func (s *OrganizationService) CreateOrganization(ctx context.Context, o *platform.Organization) error {
|
2018-06-22 05:23:11 +00:00
|
|
|
if o.Name == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "organization name is required",
|
|
|
|
}
|
2018-06-22 05:23:11 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
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")
|
2018-08-27 19:18:11 +00:00
|
|
|
SetToken(s.Token, req)
|
2018-05-14 16:26:38 +00:00
|
|
|
|
|
|
|
hc := newClient(url.Scheme, s.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-18 22:32:53 +00:00
|
|
|
defer resp.Body.Close()
|
2018-05-14 16:26:38 +00:00
|
|
|
|
2018-05-23 18:29:01 +00:00
|
|
|
// TODO(jsternberg): Should this check for a 201 explicitly?
|
2019-01-24 01:02:37 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
2018-05-23 18:29:01 +00:00
|
|
|
return err
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(o); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
// UpdateOrganization updates the organization over HTTP.
|
2018-05-14 16:26:38 +00:00
|
|
|
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")
|
2018-08-27 19:18:11 +00:00
|
|
|
SetToken(s.Token, req)
|
2018-05-16 18:59:35 +00:00
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-01-18 22:32:53 +00:00
|
|
|
defer resp.Body.Close()
|
2018-05-16 18:59:35 +00:00
|
|
|
|
2019-01-24 01:02:37 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
2018-05-23 18:29:01 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
return &o, nil
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-14 23:34:40 +00:00
|
|
|
// DeleteOrganization removes organization id over HTTP.
|
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
|
|
|
|
}
|
2018-08-27 19:18:11 +00:00
|
|
|
SetToken(s.Token, req)
|
2018-05-16 18:59:35 +00:00
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-18 22:32:53 +00:00
|
|
|
defer resp.Body.Close()
|
2018-10-29 19:10:33 +00:00
|
|
|
|
2019-01-24 01:02:37 +00:00
|
|
|
return CheckErrorStatus(http.StatusNoContent, 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
|
|
|
}
|
2018-11-02 18:21:14 +00:00
|
|
|
|
|
|
|
// hanldeGetOrganizationLog retrieves a organization log by the organizations ID.
|
|
|
|
func (h *OrgHandler) handleGetOrgLog(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetOrganizationLogRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log, _, err := h.OrganizationOperationLogService.GetOrganizationOperationLog(ctx, req.OrganizationID, req.opts)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newOrganizationLogResponse(req.OrganizationID, log)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
2018-11-02 18:21:14 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getOrganizationLogRequest struct {
|
|
|
|
OrganizationID platform.ID
|
|
|
|
opts platform.FindOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGetOrganizationLogRequest(ctx context.Context, r *http.Request) (*getOrganizationLogRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2019-01-24 00:15:42 +00:00
|
|
|
return nil, &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "url missing id",
|
|
|
|
}
|
2018-11-02 18:21:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-02-01 03:10:05 +00:00
|
|
|
opts, err := decodeFindOptions(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2018-11-02 18:21:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &getOrganizationLogRequest{
|
|
|
|
OrganizationID: i,
|
2019-02-01 03:10:05 +00:00
|
|
|
opts: *opts,
|
2018-11-02 18:21:14 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newOrganizationLogResponse(id platform.ID, es []*platform.OperationLogEntry) *operationLogResponse {
|
|
|
|
log := make([]*operationLogEntryResponse, 0, len(es))
|
|
|
|
for _, e := range es {
|
|
|
|
log = append(log, newOperationLogEntryResponse(e))
|
|
|
|
}
|
|
|
|
return &operationLogResponse{
|
|
|
|
Links: map[string]string{
|
|
|
|
"self": fmt.Sprintf("/api/v2/organizations/%s/log", id),
|
|
|
|
},
|
|
|
|
Log: log,
|
|
|
|
}
|
|
|
|
}
|