github.com/minio/madmin-go@v1.7.5/cluster-commands.go (about)

     1  //
     2  // MinIO Object Storage (c) 2021 MinIO, Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //      http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package madmin
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"net/url"
    25  	"strconv"
    26  	"time"
    27  
    28  	"github.com/minio/minio-go/v7/pkg/replication"
    29  )
    30  
    31  // SiteReplAPIVersion holds the supported version of the server Replication API
    32  const SiteReplAPIVersion = "1"
    33  
    34  // PeerSite - represents a cluster/site to be added to the set of replicated
    35  // sites.
    36  type PeerSite struct {
    37  	Name      string `json:"name"`
    38  	Endpoint  string `json:"endpoints"`
    39  	AccessKey string `json:"accessKey"`
    40  	SecretKey string `json:"secretKey"`
    41  }
    42  
    43  // Meaningful values for ReplicateAddStatus.Status
    44  const (
    45  	ReplicateAddStatusSuccess = "Requested sites were configured for replication successfully."
    46  	ReplicateAddStatusPartial = "Some sites could not be configured for replication."
    47  )
    48  
    49  // ReplicateAddStatus - returns status of add request.
    50  type ReplicateAddStatus struct {
    51  	Success                 bool   `json:"success"`
    52  	Status                  string `json:"status"`
    53  	ErrDetail               string `json:"errorDetail,omitempty"`
    54  	InitialSyncErrorMessage string `json:"initialSyncErrorMessage,omitempty"`
    55  }
    56  
    57  // SiteReplicationAdd - sends the SR add API call.
    58  func (adm *AdminClient) SiteReplicationAdd(ctx context.Context, sites []PeerSite) (ReplicateAddStatus, error) {
    59  	sitesBytes, err := json.Marshal(sites)
    60  	if err != nil {
    61  		return ReplicateAddStatus{}, nil
    62  	}
    63  	encBytes, err := EncryptData(adm.getSecretKey(), sitesBytes)
    64  	if err != nil {
    65  		return ReplicateAddStatus{}, err
    66  	}
    67  
    68  	q := make(url.Values)
    69  	q.Set("api-version", SiteReplAPIVersion)
    70  
    71  	reqData := requestData{
    72  		relPath:     adminAPIPrefix + "/site-replication/add",
    73  		content:     encBytes,
    74  		queryValues: q,
    75  	}
    76  
    77  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
    78  	defer closeResponse(resp)
    79  	if err != nil {
    80  		return ReplicateAddStatus{}, err
    81  	}
    82  
    83  	if resp.StatusCode != http.StatusOK {
    84  		return ReplicateAddStatus{}, httpRespToErrorResponse(resp)
    85  	}
    86  
    87  	b, err := ioutil.ReadAll(resp.Body)
    88  	if err != nil {
    89  		return ReplicateAddStatus{}, err
    90  	}
    91  
    92  	var res ReplicateAddStatus
    93  	if err = json.Unmarshal(b, &res); err != nil {
    94  		return ReplicateAddStatus{}, err
    95  	}
    96  
    97  	return res, nil
    98  }
    99  
   100  // SiteReplicationInfo - contains cluster replication information.
   101  type SiteReplicationInfo struct {
   102  	Enabled                 bool       `json:"enabled"`
   103  	Name                    string     `json:"name,omitempty"`
   104  	Sites                   []PeerInfo `json:"sites,omitempty"`
   105  	ServiceAccountAccessKey string     `json:"serviceAccountAccessKey,omitempty"`
   106  }
   107  
   108  // SiteReplicationInfo - returns cluster replication information.
   109  func (adm *AdminClient) SiteReplicationInfo(ctx context.Context) (info SiteReplicationInfo, err error) {
   110  	q := make(url.Values)
   111  	q.Set("api-version", SiteReplAPIVersion)
   112  
   113  	reqData := requestData{
   114  		relPath:     adminAPIPrefix + "/site-replication/info",
   115  		queryValues: q,
   116  	}
   117  
   118  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   119  	defer closeResponse(resp)
   120  	if err != nil {
   121  		return info, err
   122  	}
   123  
   124  	if resp.StatusCode != http.StatusOK {
   125  		return info, httpRespToErrorResponse(resp)
   126  	}
   127  
   128  	b, err := ioutil.ReadAll(resp.Body)
   129  	if err != nil {
   130  		return info, err
   131  	}
   132  
   133  	err = json.Unmarshal(b, &info)
   134  	return info, err
   135  }
   136  
   137  // SRPeerJoinReq - arg body for SRPeerJoin
   138  type SRPeerJoinReq struct {
   139  	SvcAcctAccessKey string              `json:"svcAcctAccessKey"`
   140  	SvcAcctSecretKey string              `json:"svcAcctSecretKey"`
   141  	SvcAcctParent    string              `json:"svcAcctParent"`
   142  	Peers            map[string]PeerInfo `json:"peers"`
   143  }
   144  
   145  // PeerInfo - contains some properties of a cluster peer.
   146  type PeerInfo struct {
   147  	Endpoint string `json:"endpoint"`
   148  	Name     string `json:"name"`
   149  	// Deployment ID is useful as it is immutable - though endpoint may
   150  	// change.
   151  	DeploymentID string `json:"deploymentID"`
   152  }
   153  
   154  // SRPeerJoin - used only by minio server to send SR join requests to peer
   155  // servers.
   156  func (adm *AdminClient) SRPeerJoin(ctx context.Context, r SRPeerJoinReq) error {
   157  	b, err := json.Marshal(r)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	encBuf, err := EncryptData(adm.getSecretKey(), b)
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	q := make(url.Values)
   167  	q.Set("api-version", SiteReplAPIVersion)
   168  
   169  	reqData := requestData{
   170  		relPath:     adminAPIPrefix + "/site-replication/peer/join",
   171  		content:     encBuf,
   172  		queryValues: q,
   173  	}
   174  
   175  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   176  	defer closeResponse(resp)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	if resp.StatusCode != http.StatusOK {
   182  		return httpRespToErrorResponse(resp)
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  // BktOp represents the bucket operation being requested.
   189  type BktOp string
   190  
   191  // BktOp value constants.
   192  const (
   193  	// make bucket and enable versioning
   194  	MakeWithVersioningBktOp BktOp = "make-with-versioning"
   195  	// add replication configuration
   196  	ConfigureReplBktOp BktOp = "configure-replication"
   197  	// delete bucket (forceDelete = off)
   198  	DeleteBucketBktOp BktOp = "delete-bucket"
   199  	// delete bucket (forceDelete = on)
   200  	ForceDeleteBucketBktOp BktOp = "force-delete-bucket"
   201  	// purge bucket
   202  	PurgeDeletedBucketOp BktOp = "purge-deleted-bucket"
   203  )
   204  
   205  // SRPeerBucketOps - tells peers to create bucket and setup replication.
   206  func (adm *AdminClient) SRPeerBucketOps(ctx context.Context, bucket string, op BktOp, opts map[string]string) error {
   207  	v := url.Values{}
   208  	v.Add("bucket", bucket)
   209  	v.Add("operation", string(op))
   210  
   211  	// For make-bucket, bucket options may be sent via `opts`
   212  	if op == MakeWithVersioningBktOp || op == DeleteBucketBktOp {
   213  		for k, val := range opts {
   214  			v.Add(k, val)
   215  		}
   216  	}
   217  
   218  	v.Set("api-version", SiteReplAPIVersion)
   219  
   220  	reqData := requestData{
   221  		queryValues: v,
   222  		relPath:     adminAPIPrefix + "/site-replication/peer/bucket-ops",
   223  	}
   224  
   225  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   226  	defer closeResponse(resp)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	if resp.StatusCode != http.StatusOK {
   232  		return httpRespToErrorResponse(resp)
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  // SRIAMItem.Type constants.
   239  const (
   240  	SRIAMItemPolicy        = "policy"
   241  	SRIAMItemSvcAcc        = "service-account"
   242  	SRIAMItemSTSAcc        = "sts-account"
   243  	SRIAMItemPolicyMapping = "policy-mapping"
   244  	SRIAMItemIAMUser       = "iam-user"
   245  	SRIAMItemGroupInfo     = "group-info"
   246  )
   247  
   248  // SRSvcAccCreate - create operation
   249  type SRSvcAccCreate struct {
   250  	Parent        string                 `json:"parent"`
   251  	AccessKey     string                 `json:"accessKey"`
   252  	SecretKey     string                 `json:"secretKey"`
   253  	Groups        []string               `json:"groups"`
   254  	Claims        map[string]interface{} `json:"claims"`
   255  	SessionPolicy json.RawMessage        `json:"sessionPolicy"`
   256  	Status        string                 `json:"status"`
   257  }
   258  
   259  // SRSvcAccUpdate - update operation
   260  type SRSvcAccUpdate struct {
   261  	AccessKey     string          `json:"accessKey"`
   262  	SecretKey     string          `json:"secretKey"`
   263  	Status        string          `json:"status"`
   264  	SessionPolicy json.RawMessage `json:"sessionPolicy"`
   265  }
   266  
   267  // SRSvcAccDelete - delete operation
   268  type SRSvcAccDelete struct {
   269  	AccessKey string `json:"accessKey"`
   270  }
   271  
   272  // SRSvcAccChange - sum-type to represent an svc account change.
   273  type SRSvcAccChange struct {
   274  	Create *SRSvcAccCreate `json:"crSvcAccCreate"`
   275  	Update *SRSvcAccUpdate `json:"crSvcAccUpdate"`
   276  	Delete *SRSvcAccDelete `json:"crSvcAccDelete"`
   277  }
   278  
   279  // SRPolicyMapping - represents mapping of a policy to a user or group.
   280  type SRPolicyMapping struct {
   281  	UserOrGroup string    `json:"userOrGroup"`
   282  	UserType    int       `json:"userType"`
   283  	IsGroup     bool      `json:"isGroup"`
   284  	Policy      string    `json:"policy"`
   285  	CreatedAt   time.Time `json:"createdAt,omitempty"`
   286  	UpdatedAt   time.Time `json:"updatedAt,omitempty"`
   287  }
   288  
   289  // SRSTSCredential - represents an STS credential to be replicated.
   290  type SRSTSCredential struct {
   291  	AccessKey           string `json:"accessKey"`
   292  	SecretKey           string `json:"secretKey"`
   293  	SessionToken        string `json:"sessionToken"`
   294  	ParentUser          string `json:"parentUser"`
   295  	ParentPolicyMapping string `json:"parentPolicyMapping,omitempty"`
   296  }
   297  
   298  // SRIAMUser - represents a regular (IAM) user to be replicated. A nil UserReq
   299  // implies that a user delete operation should be replicated on the peer cluster.
   300  type SRIAMUser struct {
   301  	AccessKey   string              `json:"accessKey"`
   302  	IsDeleteReq bool                `json:"isDeleteReq"`
   303  	UserReq     *AddOrUpdateUserReq `json:"userReq"`
   304  }
   305  
   306  // SRGroupInfo - represents a regular (IAM) user to be replicated.
   307  type SRGroupInfo struct {
   308  	UpdateReq GroupAddRemove `json:"updateReq"`
   309  }
   310  
   311  // SRIAMItem - represents an IAM object that will be copied to a peer.
   312  type SRIAMItem struct {
   313  	Type string `json:"type"`
   314  
   315  	// Name and Policy below are used when Type == SRIAMItemPolicy
   316  	Name   string          `json:"name"`
   317  	Policy json.RawMessage `json:"policy"`
   318  
   319  	// Used when Type == SRIAMItemPolicyMapping
   320  	PolicyMapping *SRPolicyMapping `json:"policyMapping"`
   321  
   322  	// Used when Type == SRIAMItemSvcAcc
   323  	SvcAccChange *SRSvcAccChange `json:"serviceAccountChange"`
   324  
   325  	// Used when Type = SRIAMItemSTSAcc
   326  	STSCredential *SRSTSCredential `json:"stsCredential"`
   327  
   328  	// Used when Type = SRIAMItemIAMUser
   329  	IAMUser *SRIAMUser `json:"iamUser"`
   330  
   331  	// Used when Type = SRIAMItemGroupInfo
   332  	GroupInfo *SRGroupInfo `json:"groupInfo"`
   333  
   334  	// UpdatedAt - timestamp of last update
   335  	UpdatedAt time.Time `json:"updatedAt,omitempty"`
   336  }
   337  
   338  // SRPeerReplicateIAMItem - copies an IAM object to a peer cluster.
   339  func (adm *AdminClient) SRPeerReplicateIAMItem(ctx context.Context, item SRIAMItem) error {
   340  	b, err := json.Marshal(item)
   341  	if err != nil {
   342  		return err
   343  	}
   344  
   345  	q := make(url.Values)
   346  	q.Add("api-version", SiteReplAPIVersion)
   347  
   348  	reqData := requestData{
   349  		relPath:     adminAPIPrefix + "/site-replication/peer/iam-item",
   350  		content:     b,
   351  		queryValues: q,
   352  	}
   353  
   354  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   355  	defer closeResponse(resp)
   356  	if err != nil {
   357  		return err
   358  	}
   359  
   360  	if resp.StatusCode != http.StatusOK {
   361  		return httpRespToErrorResponse(resp)
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  // SRBucketMeta.Type constants
   368  const (
   369  	SRBucketMetaTypePolicy           = "policy"
   370  	SRBucketMetaTypeTags             = "tags"
   371  	SRBucketMetaTypeVersionConfig    = "version-config"
   372  	SRBucketMetaTypeObjectLockConfig = "object-lock-config"
   373  	SRBucketMetaTypeSSEConfig        = "sse-config"
   374  	SRBucketMetaTypeQuotaConfig      = "quota-config"
   375  )
   376  
   377  // SRBucketMeta - represents a bucket metadata change that will be copied to a peer.
   378  type SRBucketMeta struct {
   379  	Type   string          `json:"type"`
   380  	Bucket string          `json:"bucket"`
   381  	Policy json.RawMessage `json:"policy,omitempty"`
   382  
   383  	// Since Versioning config does not have a json representation, we use
   384  	// xml byte presentation directly.
   385  	Versioning *string `json:"versioningConfig,omitempty"`
   386  
   387  	// Since tags does not have a json representation, we use its xml byte
   388  	// representation directly.
   389  	Tags *string `json:"tags,omitempty"`
   390  
   391  	// Since object lock does not have a json representation, we use its xml
   392  	// byte representation.
   393  	ObjectLockConfig *string `json:"objectLockConfig,omitempty"`
   394  
   395  	// Since SSE config does not have a json representation, we use its xml
   396  	// byte respresentation.
   397  	SSEConfig *string `json:"sseConfig,omitempty"`
   398  
   399  	// Quota has a json representation use it as is.
   400  	Quota json.RawMessage `json:"quota,omitempty"`
   401  
   402  	// UpdatedAt - timestamp of last update
   403  	UpdatedAt time.Time `json:"updatedAt,omitempty"`
   404  }
   405  
   406  // SRPeerReplicateBucketMeta - copies a bucket metadata change to a peer cluster.
   407  func (adm *AdminClient) SRPeerReplicateBucketMeta(ctx context.Context, item SRBucketMeta) error {
   408  	b, err := json.Marshal(item)
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	q := make(url.Values)
   414  	q.Set("api-version", SiteReplAPIVersion)
   415  
   416  	reqData := requestData{
   417  		relPath:     adminAPIPrefix + "/site-replication/peer/bucket-meta",
   418  		content:     b,
   419  		queryValues: q,
   420  	}
   421  
   422  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   423  	defer closeResponse(resp)
   424  	if err != nil {
   425  		return err
   426  	}
   427  
   428  	if resp.StatusCode != http.StatusOK {
   429  		return httpRespToErrorResponse(resp)
   430  	}
   431  
   432  	return nil
   433  }
   434  
   435  // SRBucketInfo - returns all the bucket metadata available for bucket
   436  type SRBucketInfo struct {
   437  	Bucket string          `json:"bucket"`
   438  	Policy json.RawMessage `json:"policy,omitempty"`
   439  
   440  	// Since Versioning config does not have a json representation, we use
   441  	// xml byte presentation directly.
   442  	Versioning *string `json:"versioningConfig,omitempty"`
   443  
   444  	// Since tags does not have a json representation, we use its xml byte
   445  	// representation directly.
   446  	Tags *string `json:"tags,omitempty"`
   447  
   448  	// Since object lock does not have a json representation, we use its xml
   449  	// byte representation.
   450  	ObjectLockConfig *string `json:"objectLockConfig,omitempty"`
   451  
   452  	// Since SSE config does not have a json representation, we use its xml
   453  	// byte respresentation.
   454  	SSEConfig *string `json:"sseConfig,omitempty"`
   455  	// replication config in json representation
   456  	ReplicationConfig *string `json:"replicationConfig,omitempty"`
   457  	// quota config in json representation
   458  	QuotaConfig *string `json:"quotaConfig,omitempty"`
   459  
   460  	// time stamps of bucket metadata updates
   461  	PolicyUpdatedAt            time.Time `json:"policyTimestamp,omitempty"`
   462  	TagConfigUpdatedAt         time.Time `json:"tagTimestamp,omitempty"`
   463  	ObjectLockConfigUpdatedAt  time.Time `json:"olockTimestamp,omitempty"`
   464  	SSEConfigUpdatedAt         time.Time `json:"sseTimestamp,omitempty"`
   465  	VersioningConfigUpdatedAt  time.Time `json:"versioningTimestamp,omitempty"`
   466  	ReplicationConfigUpdatedAt time.Time `json:"replicationConfigTimestamp,omitempty"`
   467  	QuotaConfigUpdatedAt       time.Time `json:"quotaTimestamp,omitempty"`
   468  	CreatedAt                  time.Time `json:"bucketTimestamp,omitempty"`
   469  	DeletedAt                  time.Time `json:"bucketDeletedTimestamp,omitempty"`
   470  	Location                   string    `json:"location,omitempty"`
   471  }
   472  
   473  // OpenIDProviderSettings contains info on a particular OIDC based provider.
   474  type OpenIDProviderSettings struct {
   475  	ClaimName            string
   476  	ClaimUserinfoEnabled bool
   477  	RolePolicy           string
   478  	ClientID             string
   479  	HashedClientSecret   string
   480  }
   481  
   482  // OpenIDSettings contains OpenID configuration info of a cluster.
   483  type OpenIDSettings struct {
   484  	// Enabled is true iff there is at least one OpenID provider configured.
   485  	Enabled bool
   486  	Region  string
   487  	// Map of role ARN to provider info
   488  	Roles map[string]OpenIDProviderSettings
   489  	// Info on the claim based provider (all fields are empty if not
   490  	// present)
   491  	ClaimProvider OpenIDProviderSettings
   492  }
   493  
   494  // IDPSettings contains key IDentity Provider settings to validate that all
   495  // peers have the same configuration.
   496  type IDPSettings struct {
   497  	LDAP   LDAPSettings
   498  	OpenID OpenIDSettings
   499  }
   500  
   501  // LDAPSettings contains LDAP configuration info of a cluster.
   502  type LDAPSettings struct {
   503  	IsLDAPEnabled          bool
   504  	LDAPUserDNSearchBase   string
   505  	LDAPUserDNSearchFilter string
   506  	LDAPGroupSearchBase    string
   507  	LDAPGroupSearchFilter  string
   508  }
   509  
   510  // SRPeerGetIDPSettings - fetches IDP settings from the server.
   511  func (adm *AdminClient) SRPeerGetIDPSettings(ctx context.Context) (info IDPSettings, err error) {
   512  	q := make(url.Values)
   513  	q.Set("api-version", SiteReplAPIVersion)
   514  
   515  	reqData := requestData{
   516  		relPath:     adminAPIPrefix + "/site-replication/peer/idp-settings",
   517  		queryValues: q,
   518  	}
   519  
   520  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   521  	defer closeResponse(resp)
   522  	if err != nil {
   523  		return info, err
   524  	}
   525  
   526  	if resp.StatusCode != http.StatusOK {
   527  		return info, httpRespToErrorResponse(resp)
   528  	}
   529  
   530  	b, err := ioutil.ReadAll(resp.Body)
   531  	if err != nil {
   532  		return info, err
   533  	}
   534  
   535  	err = json.Unmarshal(b, &info)
   536  	if err != nil {
   537  		// If the server is older version, the IDPSettings was =
   538  		// LDAPSettings, so we try that.
   539  		err2 := json.Unmarshal(b, &info.LDAP)
   540  		if err2 == nil {
   541  			err = nil
   542  		}
   543  	}
   544  	return info, err
   545  }
   546  
   547  // SRIAMPolicy - represents an IAM policy.
   548  type SRIAMPolicy struct {
   549  	Policy    json.RawMessage `json:"policy"`
   550  	UpdatedAt time.Time       `json:"updatedAt,omitempty"`
   551  }
   552  
   553  // SRInfo gets replication metadata for a site
   554  type SRInfo struct {
   555  	Enabled        bool
   556  	Name           string
   557  	DeploymentID   string
   558  	Buckets        map[string]SRBucketInfo       // map of bucket metadata info
   559  	Policies       map[string]SRIAMPolicy        //  map of IAM policy name to content
   560  	UserPolicies   map[string]SRPolicyMapping    // map of username -> user policy mapping
   561  	UserInfoMap    map[string]UserInfo           // map of user name to UserInfo
   562  	GroupDescMap   map[string]GroupDesc          // map of group name to GroupDesc
   563  	GroupPolicies  map[string]SRPolicyMapping    // map of groupname -> group policy mapping
   564  	ReplicationCfg map[string]replication.Config // map of bucket -> replication config
   565  }
   566  
   567  // SRMetaInfo - returns replication metadata info for a site.
   568  func (adm *AdminClient) SRMetaInfo(ctx context.Context, opts SRStatusOptions) (info SRInfo, err error) {
   569  	q := opts.getURLValues()
   570  	q.Set("api-version", SiteReplAPIVersion)
   571  
   572  	reqData := requestData{
   573  		relPath:     adminAPIPrefix + "/site-replication/metainfo",
   574  		queryValues: q,
   575  	}
   576  
   577  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   578  	defer closeResponse(resp)
   579  	if err != nil {
   580  		return info, err
   581  	}
   582  
   583  	if resp.StatusCode != http.StatusOK {
   584  		return info, httpRespToErrorResponse(resp)
   585  	}
   586  
   587  	err = json.NewDecoder(resp.Body).Decode(&info)
   588  	return info, err
   589  }
   590  
   591  // SRStatusInfo returns detailed status on site replication status
   592  type SRStatusInfo struct {
   593  	Enabled      bool
   594  	MaxBuckets   int                      // maximum buckets seen across sites
   595  	MaxUsers     int                      // maximum users seen across sites
   596  	MaxGroups    int                      // maximum groups seen across sites
   597  	MaxPolicies  int                      // maximum policies across sites
   598  	Sites        map[string]PeerInfo      // deployment->sitename
   599  	StatsSummary map[string]SRSiteSummary // map of deployment id -> site stat
   600  	// BucketStats map of bucket to slice of deployment IDs with stats. This is populated only if there are
   601  	// mismatches or if a specific bucket's stats are requested
   602  	BucketStats map[string]map[string]SRBucketStatsSummary
   603  	// PolicyStats map of policy to slice of deployment IDs with stats. This is populated only if there are
   604  	// mismatches or if a specific bucket's stats are requested
   605  	PolicyStats map[string]map[string]SRPolicyStatsSummary
   606  	// UserStats map of user to slice of deployment IDs with stats. This is populated only if there are
   607  	// mismatches or if a specific bucket's stats are requested
   608  	UserStats map[string]map[string]SRUserStatsSummary
   609  	// GroupStats map of group to slice of deployment IDs with stats. This is populated only if there are
   610  	// mismatches or if a specific bucket's stats are requested
   611  	GroupStats map[string]map[string]SRGroupStatsSummary
   612  }
   613  
   614  // SRPolicyStatsSummary has status of policy replication misses
   615  type SRPolicyStatsSummary struct {
   616  	DeploymentID   string
   617  	PolicyMismatch bool
   618  	HasPolicy      bool
   619  }
   620  
   621  // SRUserStatsSummary has status of user replication misses
   622  type SRUserStatsSummary struct {
   623  	DeploymentID     string
   624  	PolicyMismatch   bool
   625  	UserInfoMismatch bool
   626  	HasUser          bool
   627  	HasPolicyMapping bool
   628  }
   629  
   630  // SRGroupStatsSummary has status of group replication misses
   631  type SRGroupStatsSummary struct {
   632  	DeploymentID      string
   633  	PolicyMismatch    bool
   634  	HasGroup          bool
   635  	GroupDescMismatch bool
   636  	HasPolicyMapping  bool
   637  }
   638  
   639  // SRBucketStatsSummary has status of bucket metadata replication misses
   640  type SRBucketStatsSummary struct {
   641  	DeploymentID             string
   642  	HasBucket                bool
   643  	BucketMarkedDeleted      bool
   644  	TagMismatch              bool
   645  	VersioningConfigMismatch bool
   646  	OLockConfigMismatch      bool
   647  	PolicyMismatch           bool
   648  	SSEConfigMismatch        bool
   649  	ReplicationCfgMismatch   bool
   650  	QuotaCfgMismatch         bool
   651  	HasTagsSet               bool
   652  	HasOLockConfigSet        bool
   653  	HasPolicySet             bool
   654  	HasSSECfgSet             bool
   655  	HasReplicationCfg        bool
   656  	HasQuotaCfgSet           bool
   657  }
   658  
   659  // SRSiteSummary holds the count of replicated items in site replication
   660  type SRSiteSummary struct {
   661  	ReplicatedBuckets             int // count of buckets replicated across sites
   662  	ReplicatedTags                int // count of buckets with tags replicated across sites
   663  	ReplicatedBucketPolicies      int // count of policies replicated across sites
   664  	ReplicatedIAMPolicies         int // count of IAM policies replicated across sites
   665  	ReplicatedUsers               int // count of users replicated across sites
   666  	ReplicatedGroups              int // count of groups replicated across sites
   667  	ReplicatedLockConfig          int // count of object lock config replicated across sites
   668  	ReplicatedSSEConfig           int // count of SSE config replicated across sites
   669  	ReplicatedVersioningConfig    int // count of versioning config replicated across sites
   670  	ReplicatedQuotaConfig         int // count of bucket with quota config replicated across sites
   671  	ReplicatedUserPolicyMappings  int // count of user policy mappings replicated across sites
   672  	ReplicatedGroupPolicyMappings int // count of group policy mappings replicated across sites
   673  
   674  	TotalBucketsCount            int // total buckets on this site
   675  	TotalTagsCount               int // total count of buckets with tags on this site
   676  	TotalBucketPoliciesCount     int // total count of buckets with bucket policies for this site
   677  	TotalIAMPoliciesCount        int // total count of IAM policies for this site
   678  	TotalLockConfigCount         int // total count of buckets with object lock config for this site
   679  	TotalSSEConfigCount          int // total count of buckets with SSE config
   680  	TotalVersioningConfigCount   int // total count of bucekts with versioning config
   681  	TotalQuotaConfigCount        int // total count of buckets with quota config
   682  	TotalUsersCount              int // total number of users seen on this site
   683  	TotalGroupsCount             int // total number of groups seen on this site
   684  	TotalUserPolicyMappingCount  int // total number of user policy mappings seen on this site
   685  	TotalGroupPolicyMappingCount int // total number of group policy mappings seen on this site
   686  }
   687  
   688  // SREntityType specifies type of entity
   689  type SREntityType int
   690  
   691  const (
   692  	// Unspecified entity
   693  	Unspecified SREntityType = iota
   694  
   695  	// SRBucketEntity Bucket entity type
   696  	SRBucketEntity
   697  
   698  	// SRPolicyEntity Policy entity type
   699  	SRPolicyEntity
   700  
   701  	// SRUserEntity User entity type
   702  	SRUserEntity
   703  
   704  	// SRGroupEntity Group entity type
   705  	SRGroupEntity
   706  )
   707  
   708  // SRStatusOptions holds SR status options
   709  type SRStatusOptions struct {
   710  	Buckets     bool
   711  	Policies    bool
   712  	Users       bool
   713  	Groups      bool
   714  	Entity      SREntityType
   715  	EntityValue string
   716  	ShowDeleted bool
   717  }
   718  
   719  // IsEntitySet returns true if entity option is set
   720  func (o *SRStatusOptions) IsEntitySet() bool {
   721  	switch o.Entity {
   722  	case SRBucketEntity, SRPolicyEntity, SRUserEntity, SRGroupEntity:
   723  		return true
   724  	default:
   725  		return false
   726  	}
   727  }
   728  
   729  // GetSREntityType returns the SREntityType for a key
   730  func GetSREntityType(name string) SREntityType {
   731  	switch name {
   732  	case "bucket":
   733  		return SRBucketEntity
   734  	case "user":
   735  		return SRUserEntity
   736  	case "group":
   737  		return SRGroupEntity
   738  	case "policy":
   739  		return SRPolicyEntity
   740  	default:
   741  		return Unspecified
   742  	}
   743  }
   744  
   745  func (o *SRStatusOptions) getURLValues() url.Values {
   746  	urlValues := make(url.Values)
   747  	urlValues.Set("buckets", strconv.FormatBool(o.Buckets))
   748  	urlValues.Set("policies", strconv.FormatBool(o.Policies))
   749  	urlValues.Set("users", strconv.FormatBool(o.Users))
   750  	urlValues.Set("groups", strconv.FormatBool(o.Groups))
   751  	urlValues.Set("showDeleted", strconv.FormatBool(o.ShowDeleted))
   752  
   753  	if o.IsEntitySet() {
   754  		urlValues.Set("entityvalue", o.EntityValue)
   755  		switch o.Entity {
   756  		case SRBucketEntity:
   757  			urlValues.Set("entity", "bucket")
   758  		case SRPolicyEntity:
   759  			urlValues.Set("entity", "policy")
   760  		case SRUserEntity:
   761  			urlValues.Set("entity", "user")
   762  		case SRGroupEntity:
   763  			urlValues.Set("entity", "group")
   764  		}
   765  	}
   766  	return urlValues
   767  }
   768  
   769  // SRStatusInfo - returns site replication status
   770  func (adm *AdminClient) SRStatusInfo(ctx context.Context, opts SRStatusOptions) (info SRStatusInfo, err error) {
   771  	q := opts.getURLValues()
   772  	q.Set("api-version", SiteReplAPIVersion)
   773  
   774  	reqData := requestData{
   775  		relPath:     adminAPIPrefix + "/site-replication/status",
   776  		queryValues: q,
   777  	}
   778  
   779  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   780  	defer closeResponse(resp)
   781  	if err != nil {
   782  		return info, err
   783  	}
   784  
   785  	if resp.StatusCode != http.StatusOK {
   786  		return info, httpRespToErrorResponse(resp)
   787  	}
   788  
   789  	err = json.NewDecoder(resp.Body).Decode(&info)
   790  	return info, err
   791  }
   792  
   793  // ReplicateEditStatus - returns status of edit request.
   794  type ReplicateEditStatus struct {
   795  	Success   bool   `json:"success"`
   796  	Status    string `json:"status"`
   797  	ErrDetail string `json:"errorDetail,omitempty"`
   798  }
   799  
   800  // SiteReplicationEdit - sends the SR edit API call.
   801  func (adm *AdminClient) SiteReplicationEdit(ctx context.Context, site PeerInfo) (ReplicateEditStatus, error) {
   802  	sitesBytes, err := json.Marshal(site)
   803  	if err != nil {
   804  		return ReplicateEditStatus{}, nil
   805  	}
   806  	encBytes, err := EncryptData(adm.getSecretKey(), sitesBytes)
   807  	if err != nil {
   808  		return ReplicateEditStatus{}, err
   809  	}
   810  
   811  	q := make(url.Values)
   812  	q.Set("api-version", SiteReplAPIVersion)
   813  
   814  	reqData := requestData{
   815  		relPath:     adminAPIPrefix + "/site-replication/edit",
   816  		content:     encBytes,
   817  		queryValues: q,
   818  	}
   819  
   820  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   821  	defer closeResponse(resp)
   822  	if err != nil {
   823  		return ReplicateEditStatus{}, err
   824  	}
   825  
   826  	if resp.StatusCode != http.StatusOK {
   827  		return ReplicateEditStatus{}, httpRespToErrorResponse(resp)
   828  	}
   829  
   830  	var res ReplicateEditStatus
   831  	err = json.NewDecoder(resp.Body).Decode(&res)
   832  	return res, err
   833  }
   834  
   835  // SRPeerEdit - used only by minio server to update peer endpoint
   836  // for a server already in the site replication setup
   837  func (adm *AdminClient) SRPeerEdit(ctx context.Context, pi PeerInfo) error {
   838  	b, err := json.Marshal(pi)
   839  	if err != nil {
   840  		return err
   841  	}
   842  
   843  	q := make(url.Values)
   844  	q.Set("api-version", SiteReplAPIVersion)
   845  
   846  	reqData := requestData{
   847  		relPath:     adminAPIPrefix + "/site-replication/peer/edit",
   848  		content:     b,
   849  		queryValues: q,
   850  	}
   851  
   852  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   853  	defer closeResponse(resp)
   854  	if err != nil {
   855  		return err
   856  	}
   857  
   858  	if resp.StatusCode != http.StatusOK {
   859  		return httpRespToErrorResponse(resp)
   860  	}
   861  
   862  	return nil
   863  }
   864  
   865  // SiteReplicationRemove - unlinks a site from site replication
   866  func (adm *AdminClient) SiteReplicationRemove(ctx context.Context, removeReq SRRemoveReq) (st ReplicateRemoveStatus, err error) {
   867  	rmvBytes, err := json.Marshal(removeReq)
   868  	if err != nil {
   869  		return st, nil
   870  	}
   871  	q := make(url.Values)
   872  	q.Set("api-version", SiteReplAPIVersion)
   873  
   874  	reqData := requestData{
   875  		relPath:     adminAPIPrefix + "/site-replication/remove",
   876  		content:     rmvBytes,
   877  		queryValues: q,
   878  	}
   879  
   880  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   881  	defer closeResponse(resp)
   882  	if err != nil {
   883  		return st, err
   884  	}
   885  
   886  	if resp.StatusCode != http.StatusOK {
   887  		return st, httpRespToErrorResponse(resp)
   888  	}
   889  	var res ReplicateRemoveStatus
   890  	err = json.NewDecoder(resp.Body).Decode(&res)
   891  	return res, err
   892  }
   893  
   894  // SRPeerRemove - used only by minio server to unlink cluster replication
   895  // for a server already in the site replication setup
   896  func (adm *AdminClient) SRPeerRemove(ctx context.Context, removeReq SRRemoveReq) (st ReplicateRemoveStatus, err error) {
   897  	reqBytes, err := json.Marshal(removeReq)
   898  	if err != nil {
   899  		return st, err
   900  	}
   901  	q := make(url.Values)
   902  	q.Set("api-version", SiteReplAPIVersion)
   903  
   904  	reqData := requestData{
   905  		relPath:     adminAPIPrefix + "/site-replication/peer/remove",
   906  		content:     reqBytes,
   907  		queryValues: q,
   908  	}
   909  
   910  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   911  	defer closeResponse(resp)
   912  	if err != nil {
   913  		return st, err
   914  	}
   915  
   916  	if resp.StatusCode != http.StatusOK {
   917  		return st, httpRespToErrorResponse(resp)
   918  	}
   919  	return ReplicateRemoveStatus{}, nil
   920  }
   921  
   922  // ReplicateRemoveStatus - returns status of unlink request.
   923  type ReplicateRemoveStatus struct {
   924  	Status    string `json:"status"`
   925  	ErrDetail string `json:"errorDetail,omitempty"`
   926  }
   927  
   928  // SRRemoveReq - arg body for SRRemoveReq
   929  type SRRemoveReq struct {
   930  	SiteNames []string `json:"sites"`
   931  	RemoveAll bool     `json:"all"` // true if all sites are to be removed.
   932  }
   933  
   934  const (
   935  	ReplicateRemoveStatusSuccess = "Requested site(s) were removed from cluster replication successfully."
   936  	ReplicateRemoveStatusPartial = "Some site(s) could not be removed from cluster replication configuration."
   937  )
   938  
   939  type ResyncBucketStatus struct {
   940  	Bucket    string `json:"bucket"`
   941  	Status    string `json:"status"`
   942  	ErrDetail string `json:"errorDetail,omitempty"`
   943  }
   944  
   945  // SRResyncOpStatus - returns status of resync start request.
   946  type SRResyncOpStatus struct {
   947  	OpType    string               `json:"op"` // one of "start" or "cancel"
   948  	ResyncID  string               `json:"id"`
   949  	Status    string               `json:"status"`
   950  	Buckets   []ResyncBucketStatus `json:"buckets"`
   951  	ErrDetail string               `json:"errorDetail,omitempty"`
   952  }
   953  
   954  // SiteResyncOp type of resync operation
   955  type SiteResyncOp string
   956  
   957  const (
   958  	// SiteResyncStart starts a site resync operation
   959  	SiteResyncStart SiteResyncOp = "start"
   960  	// SiteResyncCancel cancels ongoing site resync
   961  	SiteResyncCancel SiteResyncOp = "cancel"
   962  )
   963  
   964  // SiteReplicationResyncOp - perform a site replication resync operation
   965  func (adm *AdminClient) SiteReplicationResyncOp(ctx context.Context, site PeerInfo, op SiteResyncOp) (SRResyncOpStatus, error) {
   966  	reqBytes, err := json.Marshal(site)
   967  	if err != nil {
   968  		return SRResyncOpStatus{}, nil
   969  	}
   970  
   971  	v := url.Values{}
   972  	v.Set("operation", string(op))
   973  	v.Set("api-version", SiteReplAPIVersion)
   974  
   975  	reqData := requestData{
   976  		relPath:     adminAPIPrefix + "/site-replication/resync/op",
   977  		content:     reqBytes,
   978  		queryValues: v,
   979  	}
   980  
   981  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   982  	defer closeResponse(resp)
   983  	if err != nil {
   984  		return SRResyncOpStatus{}, err
   985  	}
   986  
   987  	if resp.StatusCode != http.StatusOK {
   988  		return SRResyncOpStatus{}, httpRespToErrorResponse(resp)
   989  	}
   990  
   991  	var res SRResyncOpStatus
   992  	err = json.NewDecoder(resp.Body).Decode(&res)
   993  	return res, err
   994  }