646 lines
18 KiB
Go
646 lines
18 KiB
Go
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"path"
|
|
|
|
"github.com/influxdata/httprouter"
|
|
"github.com/influxdata/influxdb/v2"
|
|
pctx "github.com/influxdata/influxdb/v2/context"
|
|
"github.com/influxdata/influxdb/v2/kit/platform"
|
|
"github.com/influxdata/influxdb/v2/kit/platform/errors"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
prefixOrganizations = "/api/v2/orgs"
|
|
prefixBuckets = "/api/v2/buckets"
|
|
)
|
|
|
|
// ScraperBackend is all services and associated parameters required to construct
|
|
// the ScraperHandler.
|
|
type ScraperBackend struct {
|
|
errors.HTTPErrorHandler
|
|
log *zap.Logger
|
|
|
|
ScraperStorageService influxdb.ScraperTargetStoreService
|
|
BucketService influxdb.BucketService
|
|
OrganizationService influxdb.OrganizationService
|
|
UserService influxdb.UserService
|
|
UserResourceMappingService influxdb.UserResourceMappingService
|
|
LabelService influxdb.LabelService
|
|
}
|
|
|
|
// NewScraperBackend returns a new instance of ScraperBackend.
|
|
func NewScraperBackend(log *zap.Logger, b *APIBackend) *ScraperBackend {
|
|
return &ScraperBackend{
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
log: log,
|
|
|
|
ScraperStorageService: b.ScraperTargetStoreService,
|
|
BucketService: b.BucketService,
|
|
OrganizationService: b.OrganizationService,
|
|
UserService: b.UserService,
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
LabelService: b.LabelService,
|
|
}
|
|
}
|
|
|
|
// ScraperHandler represents an HTTP API handler for scraper targets.
|
|
type ScraperHandler struct {
|
|
*httprouter.Router
|
|
errors.HTTPErrorHandler
|
|
log *zap.Logger
|
|
UserService influxdb.UserService
|
|
UserResourceMappingService influxdb.UserResourceMappingService
|
|
LabelService influxdb.LabelService
|
|
ScraperStorageService influxdb.ScraperTargetStoreService
|
|
BucketService influxdb.BucketService
|
|
OrganizationService influxdb.OrganizationService
|
|
}
|
|
|
|
const (
|
|
prefixTargets = "/api/v2/scrapers"
|
|
targetsIDMembersPath = prefixTargets + "/:id/members"
|
|
targetsIDMembersIDPath = prefixTargets + "/:id/members/:userID"
|
|
targetsIDOwnersPath = prefixTargets + "/:id/owners"
|
|
targetsIDOwnersIDPath = prefixTargets + "/:id/owners/:userID"
|
|
targetsIDLabelsPath = prefixTargets + "/:id/labels"
|
|
targetsIDLabelsIDPath = prefixTargets + "/:id/labels/:lid"
|
|
)
|
|
|
|
// NewScraperHandler returns a new instance of ScraperHandler.
|
|
func NewScraperHandler(log *zap.Logger, b *ScraperBackend) *ScraperHandler {
|
|
h := &ScraperHandler{
|
|
Router: NewRouter(b.HTTPErrorHandler),
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
log: log,
|
|
UserService: b.UserService,
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
LabelService: b.LabelService,
|
|
ScraperStorageService: b.ScraperStorageService,
|
|
BucketService: b.BucketService,
|
|
OrganizationService: b.OrganizationService,
|
|
}
|
|
h.HandlerFunc("POST", prefixTargets, h.handlePostScraperTarget)
|
|
h.HandlerFunc("GET", prefixTargets, h.handleGetScraperTargets)
|
|
h.HandlerFunc("GET", prefixTargets+"/:id", h.handleGetScraperTarget)
|
|
h.HandlerFunc("PATCH", prefixTargets+"/:id", h.handlePatchScraperTarget)
|
|
h.HandlerFunc("DELETE", prefixTargets+"/:id", h.handleDeleteScraperTarget)
|
|
|
|
memberBackend := MemberBackend{
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
log: b.log.With(zap.String("handler", "member")),
|
|
ResourceType: influxdb.ScraperResourceType,
|
|
UserType: influxdb.Member,
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
UserService: b.UserService,
|
|
}
|
|
h.HandlerFunc("POST", targetsIDMembersPath, newPostMemberHandler(memberBackend))
|
|
h.HandlerFunc("GET", targetsIDMembersPath, newGetMembersHandler(memberBackend))
|
|
h.HandlerFunc("DELETE", targetsIDMembersIDPath, newDeleteMemberHandler(memberBackend))
|
|
|
|
ownerBackend := MemberBackend{
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
log: b.log.With(zap.String("handler", "member")),
|
|
ResourceType: influxdb.ScraperResourceType,
|
|
UserType: influxdb.Owner,
|
|
UserResourceMappingService: b.UserResourceMappingService,
|
|
UserService: b.UserService,
|
|
}
|
|
h.HandlerFunc("POST", targetsIDOwnersPath, newPostMemberHandler(ownerBackend))
|
|
h.HandlerFunc("GET", targetsIDOwnersPath, newGetMembersHandler(ownerBackend))
|
|
h.HandlerFunc("DELETE", targetsIDOwnersIDPath, newDeleteMemberHandler(ownerBackend))
|
|
|
|
labelBackend := &LabelBackend{
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
log: b.log.With(zap.String("handler", "label")),
|
|
LabelService: b.LabelService,
|
|
ResourceType: influxdb.ScraperResourceType,
|
|
}
|
|
h.HandlerFunc("GET", targetsIDLabelsPath, newGetLabelsHandler(labelBackend))
|
|
h.HandlerFunc("POST", targetsIDLabelsPath, newPostLabelHandler(labelBackend))
|
|
h.HandlerFunc("DELETE", targetsIDLabelsIDPath, newDeleteLabelHandler(labelBackend))
|
|
|
|
return h
|
|
}
|
|
|
|
// handlePostScraperTarget is HTTP handler for the POST /api/v2/scrapers route.
|
|
func (h *ScraperHandler) handlePostScraperTarget(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
req, err := decodeScraperTargetAddRequest(ctx, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
auth, err := pctx.GetAuthorizer(ctx)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := h.ScraperStorageService.AddTarget(ctx, req, auth.GetUserID()); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Scraper created", zap.String("scraper", fmt.Sprint(req)))
|
|
|
|
resp, err := h.newTargetResponse(ctx, *req)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, resp); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// handleDeleteScraperTarget is the HTTP handler for the DELETE /api/v2/scrapers/:id route.
|
|
func (h *ScraperHandler) handleDeleteScraperTarget(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
id, err := decodeScraperTargetIDRequest(ctx, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := h.ScraperStorageService.RemoveTarget(ctx, *id); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Scraper deleted", zap.String("scraperTargetID", fmt.Sprint(id)))
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// handlePatchScraperTarget is the HTTP handler for the PATCH /api/v2/scrapers/:id route.
|
|
func (h *ScraperHandler) handlePatchScraperTarget(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
update, err := decodeScraperTargetUpdateRequest(ctx, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
auth, err := pctx.GetAuthorizer(ctx)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
target, err := h.ScraperStorageService.UpdateTarget(ctx, update, auth.GetUserID())
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Scraper updated", zap.String("scraper", fmt.Sprint(target)))
|
|
|
|
resp, err := h.newTargetResponse(ctx, *target)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, resp); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (h *ScraperHandler) handleGetScraperTarget(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
id, err := decodeScraperTargetIDRequest(ctx, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
target, err := h.ScraperStorageService.GetTargetByID(ctx, *id)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Scraper retrieved", zap.String("scraper", fmt.Sprint(target)))
|
|
|
|
resp, err := h.newTargetResponse(ctx, *target)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, resp); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type getScraperTargetsRequest struct {
|
|
filter influxdb.ScraperTargetFilter
|
|
}
|
|
|
|
func decodeScraperTargetsRequest(ctx context.Context, r *http.Request) (*getScraperTargetsRequest, error) {
|
|
qp := r.URL.Query()
|
|
req := &getScraperTargetsRequest{}
|
|
|
|
initialID := platform.InvalidID()
|
|
if ids, ok := qp["id"]; ok {
|
|
req.filter.IDs = make(map[platform.ID]bool)
|
|
for _, id := range ids {
|
|
i := initialID
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
return nil, err
|
|
}
|
|
req.filter.IDs[i] = false
|
|
}
|
|
}
|
|
if name := qp.Get("name"); name != "" {
|
|
req.filter.Name = &name
|
|
}
|
|
if orgID := qp.Get("orgID"); orgID != "" {
|
|
id := platform.InvalidID()
|
|
if err := id.DecodeFromString(orgID); err != nil {
|
|
return nil, err
|
|
}
|
|
req.filter.OrgID = &id
|
|
} else if org := qp.Get("org"); org != "" {
|
|
req.filter.Org = &org
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// handleGetScraperTargets is the HTTP handler for the GET /api/v2/scrapers route.
|
|
func (h *ScraperHandler) handleGetScraperTargets(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
req, err := decodeScraperTargetsRequest(ctx, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
targets, err := h.ScraperStorageService.ListTargets(ctx, req.filter)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Scrapers retrieved", zap.String("scrapers", fmt.Sprint(targets)))
|
|
|
|
resp, err := h.newListTargetsResponse(ctx, targets)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, resp); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func decodeScraperTargetUpdateRequest(ctx context.Context, r *http.Request) (*influxdb.ScraperTarget, error) {
|
|
update := &influxdb.ScraperTarget{}
|
|
if err := json.NewDecoder(r.Body).Decode(update); err != nil {
|
|
return nil, err
|
|
}
|
|
id, err := decodeScraperTargetIDRequest(ctx, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
update.ID = *id
|
|
return update, nil
|
|
}
|
|
|
|
func decodeScraperTargetAddRequest(ctx context.Context, r *http.Request) (*influxdb.ScraperTarget, error) {
|
|
req := &influxdb.ScraperTarget{}
|
|
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
|
return nil, err
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
func decodeScraperTargetIDRequest(ctx context.Context, r *http.Request) (*platform.ID, error) {
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
id := params.ByName("id")
|
|
if id == "" {
|
|
return nil, &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Msg: "url missing id",
|
|
}
|
|
}
|
|
|
|
var i platform.ID
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &i, nil
|
|
}
|
|
|
|
// ScraperService connects to Influx via HTTP using tokens to manage scraper targets.
|
|
type ScraperService struct {
|
|
Addr string
|
|
Token string
|
|
InsecureSkipVerify bool
|
|
// OpPrefix is for update invalid ops
|
|
OpPrefix string
|
|
}
|
|
|
|
// ListTargets returns a list of all scraper targets.
|
|
func (s *ScraperService) ListTargets(ctx context.Context, filter influxdb.ScraperTargetFilter) ([]influxdb.ScraperTarget, error) {
|
|
url, err := NewURL(s.Addr, prefixTargets)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
query := url.Query()
|
|
if filter.IDs != nil {
|
|
for id := range filter.IDs {
|
|
query.Add("id", id.String())
|
|
}
|
|
}
|
|
if filter.Name != nil {
|
|
query.Set("name", *filter.Name)
|
|
}
|
|
if filter.OrgID != nil {
|
|
query.Set("orgID", filter.OrgID.String())
|
|
}
|
|
if filter.Org != nil {
|
|
query.Set("org", *filter.Org)
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", url.String(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.URL.RawQuery = query.Encode()
|
|
SetToken(s.Token, req)
|
|
|
|
hc := NewClient(url.Scheme, s.InsecureSkipVerify)
|
|
resp, err := hc.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if err := CheckError(resp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var targetsResp getTargetsResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&targetsResp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
targets := make([]influxdb.ScraperTarget, len(targetsResp.Targets))
|
|
for k, v := range targetsResp.Targets {
|
|
targets[k] = v.ScraperTarget
|
|
}
|
|
|
|
return targets, nil
|
|
}
|
|
|
|
// UpdateTarget updates a single scraper target with changeset.
|
|
// Returns the new target state after update.
|
|
func (s *ScraperService) UpdateTarget(ctx context.Context, update *influxdb.ScraperTarget, userID platform.ID) (*influxdb.ScraperTarget, error) {
|
|
if !update.ID.Valid() {
|
|
return nil, &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Op: s.OpPrefix + influxdb.OpUpdateTarget,
|
|
Msg: "provided scraper target ID has invalid format",
|
|
}
|
|
}
|
|
url, err := NewURL(s.Addr, targetIDPath(update.ID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
octets, err := json.Marshal(update)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, err := http.NewRequest("PATCH", url.String(), bytes.NewReader(octets))
|
|
if err != nil {
|
|
return nil, 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 nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if err := CheckError(resp); err != nil {
|
|
return nil, err
|
|
}
|
|
var targetResp targetResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&targetResp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &targetResp.ScraperTarget, nil
|
|
}
|
|
|
|
// AddTarget creates a new scraper target and sets target.ID with the new identifier.
|
|
func (s *ScraperService) AddTarget(ctx context.Context, target *influxdb.ScraperTarget, userID platform.ID) error {
|
|
url, err := NewURL(s.Addr, prefixTargets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !target.OrgID.Valid() {
|
|
return &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Msg: "provided organization ID has invalid format",
|
|
Op: s.OpPrefix + influxdb.OpAddTarget,
|
|
}
|
|
}
|
|
if !target.BucketID.Valid() {
|
|
return &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Msg: "provided bucket ID has invalid format",
|
|
Op: s.OpPrefix + influxdb.OpAddTarget,
|
|
}
|
|
}
|
|
|
|
octets, err := json.Marshal(target)
|
|
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
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// TODO(jsternberg): Should this check for a 201 explicitly?
|
|
if err := CheckError(resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
targetResp := new(targetResponse)
|
|
if err := json.NewDecoder(resp.Body).Decode(targetResp); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveTarget removes a scraper target by ID.
|
|
func (s *ScraperService) RemoveTarget(ctx context.Context, id platform.ID) error {
|
|
url, err := NewURL(s.Addr, targetIDPath(id))
|
|
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
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return CheckErrorStatus(http.StatusNoContent, resp)
|
|
}
|
|
|
|
// GetTargetByID returns a single target by ID.
|
|
func (s *ScraperService) GetTargetByID(ctx context.Context, id platform.ID) (*influxdb.ScraperTarget, error) {
|
|
url, err := NewURL(s.Addr, targetIDPath(id))
|
|
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
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if err := CheckError(resp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var targetResp targetResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&targetResp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &targetResp.ScraperTarget, nil
|
|
}
|
|
|
|
func targetIDPath(id platform.ID) string {
|
|
return path.Join(prefixTargets, id.String())
|
|
}
|
|
|
|
type getTargetsLinks struct {
|
|
Self string `json:"self"`
|
|
}
|
|
|
|
type getTargetsResponse struct {
|
|
Links getTargetsLinks `json:"links"`
|
|
Targets []targetResponse `json:"configurations"`
|
|
}
|
|
|
|
type targetLinks struct {
|
|
Self string `json:"self"`
|
|
Bucket string `json:"bucket,omitempty"`
|
|
Organization string `json:"organization,omitempty"`
|
|
Members string `json:"members"`
|
|
Owners string `json:"owners"`
|
|
}
|
|
|
|
type targetResponse struct {
|
|
influxdb.ScraperTarget
|
|
Org string `json:"org,omitempty"`
|
|
Bucket string `json:"bucket,omitempty"`
|
|
Links targetLinks `json:"links"`
|
|
}
|
|
|
|
func (h *ScraperHandler) newListTargetsResponse(ctx context.Context, targets []influxdb.ScraperTarget) (getTargetsResponse, error) {
|
|
res := getTargetsResponse{
|
|
Links: getTargetsLinks{
|
|
Self: prefixTargets,
|
|
},
|
|
Targets: make([]targetResponse, 0, len(targets)),
|
|
}
|
|
|
|
for _, target := range targets {
|
|
resp, err := h.newTargetResponse(ctx, target)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
res.Targets = append(res.Targets, resp)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (h *ScraperHandler) newTargetResponse(ctx context.Context, target influxdb.ScraperTarget) (targetResponse, error) {
|
|
res := targetResponse{
|
|
Links: targetLinks{
|
|
Self: targetIDPath(target.ID),
|
|
Members: fmt.Sprintf("/api/v2/scrapers/%s/members", target.ID),
|
|
Owners: fmt.Sprintf("/api/v2/scrapers/%s/owners", target.ID),
|
|
},
|
|
ScraperTarget: target,
|
|
}
|
|
bucket, err := h.BucketService.FindBucketByID(ctx, target.BucketID)
|
|
if err == nil {
|
|
res.Bucket = bucket.Name
|
|
res.BucketID = bucket.ID
|
|
res.Links.Bucket = bucketIDPath(bucket.ID)
|
|
} else {
|
|
res.BucketID = platform.InvalidID()
|
|
}
|
|
|
|
org, err := h.OrganizationService.FindOrganizationByID(ctx, target.OrgID)
|
|
if err == nil {
|
|
res.Org = org.Name
|
|
res.OrgID = org.ID
|
|
res.Links.Organization = organizationIDPath(org.ID)
|
|
} else {
|
|
res.OrgID = platform.InvalidID()
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func organizationIDPath(id platform.ID) string {
|
|
return path.Join(prefixOrganizations, id.String())
|
|
}
|
|
|
|
func bucketIDPath(id platform.ID) string {
|
|
return path.Join(prefixBuckets, id.String())
|
|
}
|