storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/madmin/remote-target-commands.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 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 18 package madmin 19 20 import ( 21 "context" 22 "encoding/json" 23 "fmt" 24 "io/ioutil" 25 "net/http" 26 "net/url" 27 "strings" 28 "time" 29 30 "storj.io/minio/pkg/auth" 31 ) 32 33 // ServiceType represents service type 34 type ServiceType string 35 36 const ( 37 // ReplicationService specifies replication service 38 ReplicationService ServiceType = "replication" 39 // ILMService specifies ilm service 40 ILMService ServiceType = "ilm" 41 ) 42 43 // IsValid returns true if ARN type represents replication or ilm 44 func (t ServiceType) IsValid() bool { 45 return t == ReplicationService || t == ILMService 46 } 47 48 // ARN is a struct to define arn. 49 type ARN struct { 50 Type ServiceType 51 ID string 52 Region string 53 Bucket string 54 } 55 56 // Empty returns true if arn struct is empty 57 func (a ARN) Empty() bool { 58 return !a.Type.IsValid() 59 } 60 func (a ARN) String() string { 61 return fmt.Sprintf("arn:minio:%s:%s:%s:%s", a.Type, a.Region, a.ID, a.Bucket) 62 } 63 64 // ParseARN return ARN struct from string in arn format. 65 func ParseARN(s string) (*ARN, error) { 66 // ARN must be in the format of arn:minio:<Type>:<REGION>:<ID>:<remote-bucket> 67 if !strings.HasPrefix(s, "arn:minio:") { 68 return nil, fmt.Errorf("Invalid ARN %s", s) 69 } 70 71 tokens := strings.Split(s, ":") 72 if len(tokens) != 6 { 73 return nil, fmt.Errorf("Invalid ARN %s", s) 74 } 75 76 if tokens[4] == "" || tokens[5] == "" { 77 return nil, fmt.Errorf("Invalid ARN %s", s) 78 } 79 80 return &ARN{ 81 Type: ServiceType(tokens[2]), 82 Region: tokens[3], 83 ID: tokens[4], 84 Bucket: tokens[5], 85 }, nil 86 } 87 88 // BucketTarget represents the target bucket and site association. 89 type BucketTarget struct { 90 SourceBucket string `json:"sourcebucket"` 91 Endpoint string `json:"endpoint"` 92 Credentials *auth.Credentials `json:"credentials"` 93 TargetBucket string `json:"targetbucket"` 94 Secure bool `json:"secure"` 95 Path string `json:"path,omitempty"` 96 API string `json:"api,omitempty"` 97 Arn string `json:"arn,omitempty"` 98 Type ServiceType `json:"type"` 99 Region string `json:"omitempty"` 100 Label string `json:"label,omitempty"` 101 BandwidthLimit int64 `json:"bandwidthlimit,omitempty"` 102 ReplicationSync bool `json:"replicationSync"` 103 HealthCheckDuration time.Duration `json:"healthCheckDuration,omitempty"` 104 } 105 106 // Clone returns shallow clone of BucketTarget without secret key in credentials 107 func (t *BucketTarget) Clone() BucketTarget { 108 return BucketTarget{ 109 SourceBucket: t.SourceBucket, 110 Endpoint: t.Endpoint, 111 TargetBucket: t.TargetBucket, 112 Credentials: &auth.Credentials{AccessKey: t.Credentials.AccessKey}, 113 Secure: t.Secure, 114 Path: t.Path, 115 API: t.Path, 116 Arn: t.Arn, 117 Type: t.Type, 118 Region: t.Region, 119 Label: t.Label, 120 BandwidthLimit: t.BandwidthLimit, 121 ReplicationSync: t.ReplicationSync, 122 HealthCheckDuration: t.HealthCheckDuration, 123 } 124 } 125 126 // URL returns target url 127 func (t BucketTarget) URL() *url.URL { 128 scheme := "http" 129 if t.Secure { 130 scheme = "https" 131 } 132 return &url.URL{ 133 Scheme: scheme, 134 Host: t.Endpoint, 135 } 136 } 137 138 // Empty returns true if struct is empty. 139 func (t BucketTarget) Empty() bool { 140 return t.String() == "" || t.Credentials == nil 141 } 142 143 func (t *BucketTarget) String() string { 144 return fmt.Sprintf("%s %s", t.Endpoint, t.TargetBucket) 145 } 146 147 // BucketTargets represents a slice of bucket targets by type and endpoint 148 type BucketTargets struct { 149 Targets []BucketTarget 150 } 151 152 // Empty returns true if struct is empty. 153 func (t BucketTargets) Empty() bool { 154 if len(t.Targets) == 0 { 155 return true 156 } 157 empty := true 158 for _, t := range t.Targets { 159 if !t.Empty() { 160 return false 161 } 162 } 163 return empty 164 } 165 166 // ListRemoteTargets - gets target(s) for this bucket 167 func (adm *AdminClient) ListRemoteTargets(ctx context.Context, bucket, arnType string) (targets []BucketTarget, err error) { 168 queryValues := url.Values{} 169 queryValues.Set("bucket", bucket) 170 queryValues.Set("type", arnType) 171 172 reqData := requestData{ 173 relPath: adminAPIPrefix + "/list-remote-targets", 174 queryValues: queryValues, 175 } 176 177 // Execute GET on /minio/admin/v3/list-remote-targets 178 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 179 180 defer closeResponse(resp) 181 if err != nil { 182 return targets, err 183 } 184 185 if resp.StatusCode != http.StatusOK { 186 return targets, httpRespToErrorResponse(resp) 187 } 188 189 b, err := ioutil.ReadAll(resp.Body) 190 if err != nil { 191 return targets, err 192 } 193 if err = json.Unmarshal(b, &targets); err != nil { 194 return targets, err 195 } 196 return targets, nil 197 } 198 199 // SetRemoteTarget sets up a remote target for this bucket 200 func (adm *AdminClient) SetRemoteTarget(ctx context.Context, bucket string, target *BucketTarget) (string, error) { 201 data, err := json.Marshal(target) 202 if err != nil { 203 return "", err 204 } 205 encData, err := EncryptData(adm.getSecretKey(), data) 206 if err != nil { 207 return "", err 208 } 209 queryValues := url.Values{} 210 queryValues.Set("bucket", bucket) 211 212 reqData := requestData{ 213 relPath: adminAPIPrefix + "/set-remote-target", 214 queryValues: queryValues, 215 content: encData, 216 } 217 218 // Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type. 219 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 220 221 defer closeResponse(resp) 222 if err != nil { 223 return "", err 224 } 225 226 if resp.StatusCode != http.StatusOK { 227 return "", httpRespToErrorResponse(resp) 228 } 229 b, err := ioutil.ReadAll(resp.Body) 230 if err != nil { 231 return "", err 232 } 233 var arn string 234 if err = json.Unmarshal(b, &arn); err != nil { 235 return "", err 236 } 237 return arn, nil 238 } 239 240 // UpdateRemoteTarget updates credentials for a remote bucket target 241 func (adm *AdminClient) UpdateRemoteTarget(ctx context.Context, target *BucketTarget) (string, error) { 242 if target == nil { 243 return "", fmt.Errorf("target cannot be nil") 244 } 245 data, err := json.Marshal(target) 246 if err != nil { 247 return "", err 248 } 249 encData, err := EncryptData(adm.getSecretKey(), data) 250 if err != nil { 251 return "", err 252 } 253 queryValues := url.Values{} 254 queryValues.Set("bucket", target.SourceBucket) 255 queryValues.Set("update", "true") 256 257 reqData := requestData{ 258 relPath: adminAPIPrefix + "/set-remote-target", 259 queryValues: queryValues, 260 content: encData, 261 } 262 263 // Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type. 264 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 265 266 defer closeResponse(resp) 267 if err != nil { 268 return "", err 269 } 270 271 if resp.StatusCode != http.StatusOK { 272 return "", httpRespToErrorResponse(resp) 273 } 274 b, err := ioutil.ReadAll(resp.Body) 275 if err != nil { 276 return "", err 277 } 278 var arn string 279 if err = json.Unmarshal(b, &arn); err != nil { 280 return "", err 281 } 282 return arn, nil 283 } 284 285 // RemoveRemoteTarget removes a remote target associated with particular ARN for this bucket 286 func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error { 287 queryValues := url.Values{} 288 queryValues.Set("bucket", bucket) 289 queryValues.Set("arn", arn) 290 291 reqData := requestData{ 292 relPath: adminAPIPrefix + "/remove-remote-target", 293 queryValues: queryValues, 294 } 295 296 // Execute PUT on /minio/admin/v3/remove-remote-target to remove a target for this bucket 297 // with specific ARN 298 resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData) 299 defer closeResponse(resp) 300 if err != nil { 301 return err 302 } 303 304 if resp.StatusCode != http.StatusNoContent { 305 return httpRespToErrorResponse(resp) 306 } 307 return nil 308 }