github.com/minio/madmin-go/v2@v2.2.1/cluster-commands.go (about)

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