mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 02:01:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			431 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) 2015-2021 MinIO, Inc.
 | 
						|
//
 | 
						|
// This file is part of MinIO Object Storage stack
 | 
						|
//
 | 
						|
// This program is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/http"
 | 
						|
 | 
						|
	"github.com/gorilla/mux"
 | 
						|
	"github.com/minio/madmin-go"
 | 
						|
 | 
						|
	"github.com/minio/minio/internal/logger"
 | 
						|
	"github.com/minio/pkg/bucket/policy"
 | 
						|
	iampolicy "github.com/minio/pkg/iam/policy"
 | 
						|
)
 | 
						|
 | 
						|
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
 | 
						|
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SiteReplicationAdd")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var sites []madmin.PeerSite
 | 
						|
	if err := parseJSONBody(ctx, r.Body, &sites, cred.SecretKey); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	status, err := globalSiteReplicationSys.AddPeerClusters(ctx, sites)
 | 
						|
	if err != nil {
 | 
						|
		logger.LogIf(ctx, err)
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	body, err := json.Marshal(status)
 | 
						|
	if err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	writeSuccessResponseJSON(w, body)
 | 
						|
}
 | 
						|
 | 
						|
// SRPeerJoin - PUT /minio/admin/v3/site-replication/join
 | 
						|
//
 | 
						|
// used internally to tell current cluster to enable SR with
 | 
						|
// the provided peer clusters and service account.
 | 
						|
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SRPeerJoin")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var joinArg madmin.SRPeerJoinReq
 | 
						|
	if err := parseJSONBody(ctx, r.Body, &joinArg, cred.SecretKey); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err := globalSiteReplicationSys.PeerJoinReq(ctx, joinArg); err != nil {
 | 
						|
		logger.LogIf(ctx, err)
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SRPeerBucketOps - PUT /minio/admin/v3/site-replication/bucket-ops?bucket=x&operation=y
 | 
						|
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SRPeerBucketOps")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	vars := mux.Vars(r)
 | 
						|
	bucket := vars["bucket"]
 | 
						|
	operation := madmin.BktOp(vars["operation"])
 | 
						|
 | 
						|
	var err error
 | 
						|
	switch operation {
 | 
						|
	default:
 | 
						|
		err = errSRInvalidRequest(errInvalidArgument)
 | 
						|
	case madmin.MakeWithVersioningBktOp:
 | 
						|
		_, isLockEnabled := r.Form["lockEnabled"]
 | 
						|
		_, isVersioningEnabled := r.Form["versioningEnabled"]
 | 
						|
		opts := BucketOptions{
 | 
						|
			Location:          r.Form.Get("location"),
 | 
						|
			LockEnabled:       isLockEnabled,
 | 
						|
			VersioningEnabled: isVersioningEnabled,
 | 
						|
		}
 | 
						|
		err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
 | 
						|
	case madmin.ConfigureReplBktOp:
 | 
						|
		err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
 | 
						|
	case madmin.DeleteBucketBktOp:
 | 
						|
		err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, false)
 | 
						|
	case madmin.ForceDeleteBucketBktOp:
 | 
						|
		err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, true)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		logger.LogIf(ctx, err)
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SRPeerReplicateIAMItem - PUT /minio/admin/v3/site-replication/iam-item
 | 
						|
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SRPeerReplicateIAMItem")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var item madmin.SRIAMItem
 | 
						|
	if err := parseJSONBody(ctx, r.Body, &item, ""); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var err error
 | 
						|
	switch item.Type {
 | 
						|
	default:
 | 
						|
		err = errSRInvalidRequest(errInvalidArgument)
 | 
						|
	case madmin.SRIAMItemPolicy:
 | 
						|
		if item.Policy == nil {
 | 
						|
			err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil)
 | 
						|
		} else {
 | 
						|
			policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy))
 | 
						|
			if perr != nil {
 | 
						|
				writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
 | 
						|
				return
 | 
						|
			}
 | 
						|
			if policy.IsEmpty() {
 | 
						|
				err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil)
 | 
						|
			} else {
 | 
						|
				err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, policy)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case madmin.SRIAMItemSvcAcc:
 | 
						|
		err = globalSiteReplicationSys.PeerSvcAccChangeHandler(ctx, item.SvcAccChange)
 | 
						|
	case madmin.SRIAMItemPolicyMapping:
 | 
						|
		err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping)
 | 
						|
	case madmin.SRIAMItemSTSAcc:
 | 
						|
		err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential)
 | 
						|
	case madmin.SRIAMItemIAMUser:
 | 
						|
		err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser)
 | 
						|
	case madmin.SRIAMItemGroupInfo:
 | 
						|
		err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		logger.LogIf(ctx, err)
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/bucket-meta
 | 
						|
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SRPeerReplicateBucketItem")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var item madmin.SRBucketMeta
 | 
						|
	if err := parseJSONBody(ctx, r.Body, &item, ""); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var err error
 | 
						|
	switch item.Type {
 | 
						|
	default:
 | 
						|
		err = errSRInvalidRequest(errInvalidArgument)
 | 
						|
	case madmin.SRBucketMetaTypePolicy:
 | 
						|
		if item.Policy == nil {
 | 
						|
			err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil)
 | 
						|
		} else {
 | 
						|
			bktPolicy, berr := policy.ParseConfig(bytes.NewReader(item.Policy), item.Bucket)
 | 
						|
			if berr != nil {
 | 
						|
				writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
 | 
						|
				return
 | 
						|
			}
 | 
						|
			if bktPolicy.IsEmpty() {
 | 
						|
				err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil)
 | 
						|
			} else {
 | 
						|
				err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, bktPolicy)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case madmin.SRBucketMetaTypeQuotaConfig:
 | 
						|
		if item.Quota == nil {
 | 
						|
			err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, nil)
 | 
						|
		} else {
 | 
						|
			quotaConfig, err := parseBucketQuota(item.Bucket, item.Quota)
 | 
						|
			if err != nil {
 | 
						|
				writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
				return
 | 
						|
			}
 | 
						|
			if err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, quotaConfig); err != nil {
 | 
						|
				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case madmin.SRBucketMetaTypeTags:
 | 
						|
		err = globalSiteReplicationSys.PeerBucketTaggingHandler(ctx, item.Bucket, item.Tags)
 | 
						|
	case madmin.SRBucketMetaTypeObjectLockConfig:
 | 
						|
		err = globalSiteReplicationSys.PeerBucketObjectLockConfigHandler(ctx, item.Bucket, item.ObjectLockConfig)
 | 
						|
	case madmin.SRBucketMetaTypeSSEConfig:
 | 
						|
		err = globalSiteReplicationSys.PeerBucketSSEConfigHandler(ctx, item.Bucket, item.SSEConfig)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		logger.LogIf(ctx, err)
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SiteReplicationDisable - PUT /minio/admin/v3/site-replication/disable
 | 
						|
func (a adminAPIHandlers) SiteReplicationDisable(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SiteReplicationDisable")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationDisableAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SiteReplicationInfo - GET /minio/admin/v3/site-replication/info
 | 
						|
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SiteReplicationInfo")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	info, err := globalSiteReplicationSys.GetClusterInfo(ctx)
 | 
						|
	if err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err = json.NewEncoder(w).Encode(info); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SiteReplicationGetIDPSettings")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	idpSettings := globalSiteReplicationSys.GetIDPSettings(ctx)
 | 
						|
	if err := json.NewEncoder(w).Encode(idpSettings); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptionKey string) error {
 | 
						|
	data, err := ioutil.ReadAll(body)
 | 
						|
	if err != nil {
 | 
						|
		return SRError{
 | 
						|
			Cause: err,
 | 
						|
			Code:  ErrSiteReplicationInvalidRequest,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if encryptionKey != "" {
 | 
						|
		data, err = madmin.DecryptData(encryptionKey, bytes.NewReader(data))
 | 
						|
		if err != nil {
 | 
						|
			logger.LogIf(ctx, err)
 | 
						|
			return SRError{
 | 
						|
				Cause: err,
 | 
						|
				Code:  ErrSiteReplicationInvalidRequest,
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return json.Unmarshal(data, v)
 | 
						|
}
 | 
						|
 | 
						|
// SiteReplicationStatus - GET /minio/admin/v3/site-replication/status
 | 
						|
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SiteReplicationStatus")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	info, err := globalSiteReplicationSys.SiteReplicationStatus(ctx, objectAPI)
 | 
						|
	if err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err = json.NewEncoder(w).Encode(info); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SiteReplicationMetaInfo - GET /minio/admin/v3/site-replication/metainfo
 | 
						|
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SiteReplicationMetaInfo")
 | 
						|
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	info, err := globalSiteReplicationSys.SiteReplicationMetaInfo(ctx, objectAPI)
 | 
						|
	if err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err = json.NewEncoder(w).Encode(info); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SiteReplicationEdit - PUT /minio/admin/v3/site-replication/edit
 | 
						|
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SiteReplicationEdit")
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	var site madmin.PeerInfo
 | 
						|
	err := parseJSONBody(ctx, r.Body, &site, cred.SecretKey)
 | 
						|
	if err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	status, err := globalSiteReplicationSys.EditPeerCluster(ctx, site)
 | 
						|
	if err != nil {
 | 
						|
		logger.LogIf(ctx, err)
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	body, err := json.Marshal(status)
 | 
						|
	if err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	writeSuccessResponseJSON(w, body)
 | 
						|
}
 | 
						|
 | 
						|
// SRPeerEdit - PUT /minio/admin/v3/site-replication/peer/edit
 | 
						|
//
 | 
						|
// used internally to tell current cluster to update endpoint for peer
 | 
						|
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
 | 
						|
	ctx := newContext(r, w, "SRPeerEdit")
 | 
						|
	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
 | 
						|
 | 
						|
	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
 | 
						|
	if objectAPI == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var pi madmin.PeerInfo
 | 
						|
	if err := parseJSONBody(ctx, r.Body, &pi, ""); err != nil {
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err := globalSiteReplicationSys.PeerEditReq(ctx, pi); err != nil {
 | 
						|
		logger.LogIf(ctx, err)
 | 
						|
		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 |