github.com/minio/madmin-go@v1.7.5/remote-target-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 "fmt" 23 "io/ioutil" 24 "net/http" 25 "net/url" 26 "strings" 27 "time" 28 ) 29 30 // ServiceType represents service type 31 type ServiceType string 32 33 const ( 34 // ReplicationService specifies replication service 35 ReplicationService ServiceType = "replication" 36 ) 37 38 // IsValid returns true if ARN type represents replication 39 func (t ServiceType) IsValid() bool { 40 return t == ReplicationService 41 } 42 43 // ARN is a struct to define arn. 44 type ARN struct { 45 Type ServiceType 46 ID string 47 Region string 48 Bucket string 49 } 50 51 // Empty returns true if arn struct is empty 52 func (a ARN) Empty() bool { 53 return !a.Type.IsValid() 54 } 55 56 func (a ARN) String() string { 57 return fmt.Sprintf("arn:minio:%s:%s:%s:%s", a.Type, a.Region, a.ID, a.Bucket) 58 } 59 60 // ParseARN return ARN struct from string in arn format. 61 func ParseARN(s string) (*ARN, error) { 62 // ARN must be in the format of arn:minio:<Type>:<REGION>:<ID>:<remote-bucket> 63 if !strings.HasPrefix(s, "arn:minio:") { 64 return nil, fmt.Errorf("Invalid ARN %s", s) 65 } 66 67 tokens := strings.Split(s, ":") 68 if len(tokens) != 6 { 69 return nil, fmt.Errorf("Invalid ARN %s", s) 70 } 71 72 if tokens[4] == "" || tokens[5] == "" { 73 return nil, fmt.Errorf("Invalid ARN %s", s) 74 } 75 76 return &ARN{ 77 Type: ServiceType(tokens[2]), 78 Region: tokens[3], 79 ID: tokens[4], 80 Bucket: tokens[5], 81 }, nil 82 } 83 84 // BucketTarget represents the target bucket and site association. 85 type BucketTarget struct { 86 SourceBucket string `json:"sourcebucket"` 87 Endpoint string `json:"endpoint"` 88 Credentials *Credentials `json:"credentials"` 89 TargetBucket string `json:"targetbucket"` 90 Secure bool `json:"secure"` 91 Path string `json:"path,omitempty"` 92 API string `json:"api,omitempty"` 93 Arn string `json:"arn,omitempty"` 94 Type ServiceType `json:"type"` 95 Region string `json:"omitempty"` 96 BandwidthLimit int64 `json:"bandwidthlimit,omitempty"` 97 ReplicationSync bool `json:"replicationSync"` 98 StorageClass string `json:"storageclass,omitempty"` 99 HealthCheckDuration time.Duration `json:"healthCheckDuration,omitempty"` 100 DisableProxy bool `json:"disableProxy"` 101 ResetBeforeDate time.Time `json:"resetBeforeDate,omitempty"` 102 ResetID string `json:"resetID,omitempty"` 103 } 104 105 // Clone returns shallow clone of BucketTarget without secret key in credentials 106 func (t *BucketTarget) Clone() BucketTarget { 107 return BucketTarget{ 108 SourceBucket: t.SourceBucket, 109 Endpoint: t.Endpoint, 110 TargetBucket: t.TargetBucket, 111 Credentials: &Credentials{AccessKey: t.Credentials.AccessKey}, 112 Secure: t.Secure, 113 Path: t.Path, 114 API: t.API, 115 Arn: t.Arn, 116 Type: t.Type, 117 Region: t.Region, 118 BandwidthLimit: t.BandwidthLimit, 119 ReplicationSync: t.ReplicationSync, 120 StorageClass: t.StorageClass, // target storage class 121 HealthCheckDuration: t.HealthCheckDuration, 122 DisableProxy: t.DisableProxy, 123 ResetBeforeDate: t.ResetBeforeDate, 124 ResetID: t.ResetID, 125 } 126 } 127 128 // URL returns target url 129 func (t BucketTarget) URL() *url.URL { 130 scheme := "http" 131 if t.Secure { 132 scheme = "https" 133 } 134 return &url.URL{ 135 Scheme: scheme, 136 Host: t.Endpoint, 137 } 138 } 139 140 // Empty returns true if struct is empty. 141 func (t BucketTarget) Empty() bool { 142 return t.String() == "" || t.Credentials == nil 143 } 144 145 func (t *BucketTarget) String() string { 146 return fmt.Sprintf("%s %s", t.Endpoint, t.TargetBucket) 147 } 148 149 // BucketTargets represents a slice of bucket targets by type and endpoint 150 type BucketTargets struct { 151 Targets []BucketTarget 152 } 153 154 // Empty returns true if struct is empty. 155 func (t BucketTargets) Empty() bool { 156 if len(t.Targets) == 0 { 157 return true 158 } 159 empty := true 160 for _, t := range t.Targets { 161 if !t.Empty() { 162 return false 163 } 164 } 165 return empty 166 } 167 168 // ListRemoteTargets - gets target(s) for this bucket 169 func (adm *AdminClient) ListRemoteTargets(ctx context.Context, bucket, arnType string) (targets []BucketTarget, err error) { 170 queryValues := url.Values{} 171 queryValues.Set("bucket", bucket) 172 queryValues.Set("type", arnType) 173 174 reqData := requestData{ 175 relPath: adminAPIPrefix + "/list-remote-targets", 176 queryValues: queryValues, 177 } 178 179 // Execute GET on /minio/admin/v3/list-remote-targets 180 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 181 182 defer closeResponse(resp) 183 if err != nil { 184 return targets, err 185 } 186 187 if resp.StatusCode != http.StatusOK { 188 return targets, httpRespToErrorResponse(resp) 189 } 190 191 b, err := ioutil.ReadAll(resp.Body) 192 if err != nil { 193 return targets, err 194 } 195 if err = json.Unmarshal(b, &targets); err != nil { 196 return targets, err 197 } 198 return targets, nil 199 } 200 201 // SetRemoteTarget sets up a remote target for this bucket 202 func (adm *AdminClient) SetRemoteTarget(ctx context.Context, bucket string, target *BucketTarget) (string, error) { 203 data, err := json.Marshal(target) 204 if err != nil { 205 return "", err 206 } 207 encData, err := EncryptData(adm.getSecretKey(), data) 208 if err != nil { 209 return "", err 210 } 211 queryValues := url.Values{} 212 queryValues.Set("bucket", bucket) 213 214 reqData := requestData{ 215 relPath: adminAPIPrefix + "/set-remote-target", 216 queryValues: queryValues, 217 content: encData, 218 } 219 220 // Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type. 221 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 222 223 defer closeResponse(resp) 224 if err != nil { 225 return "", err 226 } 227 228 if resp.StatusCode != http.StatusOK { 229 return "", httpRespToErrorResponse(resp) 230 } 231 b, err := ioutil.ReadAll(resp.Body) 232 if err != nil { 233 return "", err 234 } 235 var arn string 236 if err = json.Unmarshal(b, &arn); err != nil { 237 return "", err 238 } 239 return arn, nil 240 } 241 242 // TargetUpdateType - type of update on the remote target 243 type TargetUpdateType int 244 245 const ( 246 // CredentialsUpdateType update creds 247 CredentialsUpdateType TargetUpdateType = 1 + iota 248 // SyncUpdateType update synchronous replication setting 249 SyncUpdateType 250 // ProxyUpdateType update proxy setting 251 ProxyUpdateType 252 // BandwidthLimitUpdateType update bandwidth limit 253 BandwidthLimitUpdateType 254 // HealthCheckDurationUpdateType update health check duration 255 HealthCheckDurationUpdateType 256 // PathUpdateType update Path 257 PathUpdateType 258 // ResetUpdateType sets ResetBeforeDate and ResetID on a bucket target 259 ResetUpdateType 260 ) 261 262 // GetTargetUpdateOps returns a slice of update operations being 263 // performed with `mc admin bucket remote edit` 264 func GetTargetUpdateOps(values url.Values) []TargetUpdateType { 265 var ops []TargetUpdateType 266 if values.Get("update") != "true" { 267 return ops 268 } 269 if values.Get("creds") == "true" { 270 ops = append(ops, CredentialsUpdateType) 271 } 272 if values.Get("sync") == "true" { 273 ops = append(ops, SyncUpdateType) 274 } 275 if values.Get("proxy") == "true" { 276 ops = append(ops, ProxyUpdateType) 277 } 278 if values.Get("healthcheck") == "true" { 279 ops = append(ops, HealthCheckDurationUpdateType) 280 } 281 if values.Get("bandwidth") == "true" { 282 ops = append(ops, BandwidthLimitUpdateType) 283 } 284 if values.Get("path") == "true" { 285 ops = append(ops, PathUpdateType) 286 } 287 return ops 288 } 289 290 // UpdateRemoteTarget updates credentials for a remote bucket target 291 func (adm *AdminClient) UpdateRemoteTarget(ctx context.Context, target *BucketTarget, ops ...TargetUpdateType) (string, error) { 292 if target == nil { 293 return "", fmt.Errorf("target cannot be nil") 294 } 295 data, err := json.Marshal(target) 296 if err != nil { 297 return "", err 298 } 299 encData, err := EncryptData(adm.getSecretKey(), data) 300 if err != nil { 301 return "", err 302 } 303 queryValues := url.Values{} 304 queryValues.Set("bucket", target.SourceBucket) 305 queryValues.Set("update", "true") 306 307 for _, op := range ops { 308 switch op { 309 case CredentialsUpdateType: 310 queryValues.Set("creds", "true") 311 case SyncUpdateType: 312 queryValues.Set("sync", "true") 313 case ProxyUpdateType: 314 queryValues.Set("proxy", "true") 315 case BandwidthLimitUpdateType: 316 queryValues.Set("bandwidth", "true") 317 case HealthCheckDurationUpdateType: 318 queryValues.Set("healthcheck", "true") 319 case PathUpdateType: 320 queryValues.Set("path", "true") 321 } 322 } 323 324 reqData := requestData{ 325 relPath: adminAPIPrefix + "/set-remote-target", 326 queryValues: queryValues, 327 content: encData, 328 } 329 330 // Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type. 331 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 332 333 defer closeResponse(resp) 334 if err != nil { 335 return "", err 336 } 337 338 if resp.StatusCode != http.StatusOK { 339 return "", httpRespToErrorResponse(resp) 340 } 341 b, err := ioutil.ReadAll(resp.Body) 342 if err != nil { 343 return "", err 344 } 345 var arn string 346 if err = json.Unmarshal(b, &arn); err != nil { 347 return "", err 348 } 349 return arn, nil 350 } 351 352 // RemoveRemoteTarget removes a remote target associated with particular ARN for this bucket 353 func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error { 354 queryValues := url.Values{} 355 queryValues.Set("bucket", bucket) 356 queryValues.Set("arn", arn) 357 358 reqData := requestData{ 359 relPath: adminAPIPrefix + "/remove-remote-target", 360 queryValues: queryValues, 361 } 362 363 // Execute PUT on /minio/admin/v3/remove-remote-target to remove a target for this bucket 364 // with specific ARN 365 resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData) 366 defer closeResponse(resp) 367 if err != nil { 368 return err 369 } 370 371 if resp.StatusCode != http.StatusNoContent { 372 return httpRespToErrorResponse(resp) 373 } 374 return nil 375 }