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 }