storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/gateway-common.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017-2019 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 cmd 18 19 import ( 20 "context" 21 "net" 22 "net/http" 23 "strings" 24 "time" 25 26 "storj.io/minio/cmd/config" 27 xhttp "storj.io/minio/cmd/http" 28 "storj.io/minio/cmd/logger" 29 "storj.io/minio/pkg/env" 30 "storj.io/minio/pkg/hash" 31 xnet "storj.io/minio/pkg/net" 32 33 minio "github.com/minio/minio-go/v7" 34 ) 35 36 var ( 37 // CanonicalizeETag provides canonicalizeETag function alias. 38 CanonicalizeETag = canonicalizeETag 39 40 // MustGetUUID function alias. 41 MustGetUUID = mustGetUUID 42 43 // CleanMetadataKeys provides cleanMetadataKeys function alias. 44 CleanMetadataKeys = cleanMetadataKeys 45 46 // PathJoin function alias. 47 PathJoin = pathJoin 48 49 // ListObjects function alias. 50 ListObjects = listObjects 51 52 // FilterListEntries function alias. 53 FilterListEntries = filterListEntries 54 55 // IsStringEqual is string equal. 56 IsStringEqual = isStringEqual 57 ) 58 59 // FromMinioClientMetadata converts minio metadata to map[string]string 60 func FromMinioClientMetadata(metadata map[string][]string) map[string]string { 61 mm := make(map[string]string, len(metadata)) 62 for k, v := range metadata { 63 mm[http.CanonicalHeaderKey(k)] = v[0] 64 } 65 return mm 66 } 67 68 // FromMinioClientObjectPart converts minio ObjectPart to PartInfo 69 func FromMinioClientObjectPart(op minio.ObjectPart) PartInfo { 70 return PartInfo{ 71 Size: op.Size, 72 ETag: canonicalizeETag(op.ETag), 73 LastModified: op.LastModified, 74 PartNumber: op.PartNumber, 75 } 76 } 77 78 // FromMinioClientListPartsInfo converts minio ListObjectPartsResult to ListPartsInfo 79 func FromMinioClientListPartsInfo(lopr minio.ListObjectPartsResult) ListPartsInfo { 80 // Convert minio ObjectPart to PartInfo 81 fromMinioClientObjectParts := func(parts []minio.ObjectPart) []PartInfo { 82 toParts := make([]PartInfo, len(parts)) 83 for i, part := range parts { 84 toParts[i] = FromMinioClientObjectPart(part) 85 } 86 return toParts 87 } 88 89 return ListPartsInfo{ 90 UploadID: lopr.UploadID, 91 Bucket: lopr.Bucket, 92 Object: lopr.Key, 93 StorageClass: "", 94 PartNumberMarker: lopr.PartNumberMarker, 95 NextPartNumberMarker: lopr.NextPartNumberMarker, 96 MaxParts: lopr.MaxParts, 97 IsTruncated: lopr.IsTruncated, 98 Parts: fromMinioClientObjectParts(lopr.ObjectParts), 99 } 100 } 101 102 // FromMinioClientListMultipartsInfo converts minio ListMultipartUploadsResult to ListMultipartsInfo 103 func FromMinioClientListMultipartsInfo(lmur minio.ListMultipartUploadsResult) ListMultipartsInfo { 104 uploads := make([]MultipartInfo, len(lmur.Uploads)) 105 106 for i, um := range lmur.Uploads { 107 uploads[i] = MultipartInfo{ 108 Object: um.Key, 109 UploadID: um.UploadID, 110 Initiated: um.Initiated, 111 } 112 } 113 114 commonPrefixes := make([]string, len(lmur.CommonPrefixes)) 115 for i, cp := range lmur.CommonPrefixes { 116 commonPrefixes[i] = cp.Prefix 117 } 118 119 return ListMultipartsInfo{ 120 KeyMarker: lmur.KeyMarker, 121 UploadIDMarker: lmur.UploadIDMarker, 122 NextKeyMarker: lmur.NextKeyMarker, 123 NextUploadIDMarker: lmur.NextUploadIDMarker, 124 MaxUploads: int(lmur.MaxUploads), 125 IsTruncated: lmur.IsTruncated, 126 Uploads: uploads, 127 Prefix: lmur.Prefix, 128 Delimiter: lmur.Delimiter, 129 CommonPrefixes: commonPrefixes, 130 EncodingType: lmur.EncodingType, 131 } 132 133 } 134 135 // FromMinioClientObjectInfo converts minio ObjectInfo to gateway ObjectInfo 136 func FromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo { 137 userDefined := FromMinioClientMetadata(oi.Metadata) 138 userDefined[xhttp.ContentType] = oi.ContentType 139 140 return ObjectInfo{ 141 Bucket: bucket, 142 Name: oi.Key, 143 ModTime: oi.LastModified, 144 Size: oi.Size, 145 ETag: canonicalizeETag(oi.ETag), 146 UserDefined: userDefined, 147 ContentType: oi.ContentType, 148 ContentEncoding: oi.Metadata.Get(xhttp.ContentEncoding), 149 StorageClass: oi.StorageClass, 150 Expires: oi.Expires, 151 } 152 } 153 154 // FromMinioClientListBucketV2Result converts minio ListBucketResult to ListObjectsInfo 155 func FromMinioClientListBucketV2Result(bucket string, result minio.ListBucketV2Result) ListObjectsV2Info { 156 objects := make([]ObjectInfo, len(result.Contents)) 157 158 for i, oi := range result.Contents { 159 objects[i] = FromMinioClientObjectInfo(bucket, oi) 160 } 161 162 prefixes := make([]string, len(result.CommonPrefixes)) 163 for i, p := range result.CommonPrefixes { 164 prefixes[i] = p.Prefix 165 } 166 167 return ListObjectsV2Info{ 168 IsTruncated: result.IsTruncated, 169 Prefixes: prefixes, 170 Objects: objects, 171 172 ContinuationToken: result.ContinuationToken, 173 NextContinuationToken: result.NextContinuationToken, 174 } 175 } 176 177 // FromMinioClientListBucketResult converts minio ListBucketResult to ListObjectsInfo 178 func FromMinioClientListBucketResult(bucket string, result minio.ListBucketResult) ListObjectsInfo { 179 objects := make([]ObjectInfo, len(result.Contents)) 180 181 for i, oi := range result.Contents { 182 objects[i] = FromMinioClientObjectInfo(bucket, oi) 183 } 184 185 prefixes := make([]string, len(result.CommonPrefixes)) 186 for i, p := range result.CommonPrefixes { 187 prefixes[i] = p.Prefix 188 } 189 190 return ListObjectsInfo{ 191 IsTruncated: result.IsTruncated, 192 NextMarker: result.NextMarker, 193 Prefixes: prefixes, 194 Objects: objects, 195 } 196 } 197 198 // FromMinioClientListBucketResultToV2Info converts minio ListBucketResult to ListObjectsV2Info 199 func FromMinioClientListBucketResultToV2Info(bucket string, result minio.ListBucketResult) ListObjectsV2Info { 200 objects := make([]ObjectInfo, len(result.Contents)) 201 202 for i, oi := range result.Contents { 203 objects[i] = FromMinioClientObjectInfo(bucket, oi) 204 } 205 206 prefixes := make([]string, len(result.CommonPrefixes)) 207 for i, p := range result.CommonPrefixes { 208 prefixes[i] = p.Prefix 209 } 210 211 return ListObjectsV2Info{ 212 IsTruncated: result.IsTruncated, 213 Prefixes: prefixes, 214 Objects: objects, 215 ContinuationToken: result.Marker, 216 NextContinuationToken: result.NextMarker, 217 } 218 } 219 220 // ToMinioClientObjectInfoMetadata convertes metadata to map[string][]string 221 func ToMinioClientObjectInfoMetadata(metadata map[string]string) map[string][]string { 222 mm := make(map[string][]string, len(metadata)) 223 for k, v := range metadata { 224 mm[http.CanonicalHeaderKey(k)] = []string{v} 225 } 226 return mm 227 } 228 229 // ToMinioClientMetadata converts metadata to map[string]string 230 func ToMinioClientMetadata(metadata map[string]string) map[string]string { 231 mm := make(map[string]string, len(metadata)) 232 for k, v := range metadata { 233 mm[http.CanonicalHeaderKey(k)] = v 234 } 235 return mm 236 } 237 238 // ToMinioClientCompletePart converts CompletePart to minio CompletePart 239 func ToMinioClientCompletePart(part CompletePart) minio.CompletePart { 240 return minio.CompletePart{ 241 ETag: part.ETag, 242 PartNumber: part.PartNumber, 243 } 244 } 245 246 // ToMinioClientCompleteParts converts []CompletePart to minio []CompletePart 247 func ToMinioClientCompleteParts(parts []CompletePart) []minio.CompletePart { 248 mparts := make([]minio.CompletePart, len(parts)) 249 for i, part := range parts { 250 mparts[i] = ToMinioClientCompletePart(part) 251 } 252 return mparts 253 } 254 255 // IsBackendOnline - verifies if the backend is reachable 256 // by performing a GET request on the URL. returns 'true' 257 // if backend is reachable. 258 func IsBackendOnline(ctx context.Context, host string) bool { 259 var d net.Dialer 260 261 ctx, cancel := context.WithTimeout(ctx, 1*time.Second) 262 defer cancel() 263 264 conn, err := d.DialContext(ctx, "tcp", host) 265 if err != nil { 266 return false 267 } 268 269 conn.Close() 270 return true 271 } 272 273 // ErrorRespToObjectError converts MinIO errors to minio object layer errors. 274 func ErrorRespToObjectError(err error, params ...string) error { 275 if err == nil { 276 return nil 277 } 278 279 bucket := "" 280 object := "" 281 if len(params) >= 1 { 282 bucket = params[0] 283 } 284 if len(params) == 2 { 285 object = params[1] 286 } 287 288 if xnet.IsNetworkOrHostDown(err, false) { 289 return BackendDown{} 290 } 291 292 minioErr, ok := err.(minio.ErrorResponse) 293 if !ok { 294 // We don't interpret non MinIO errors. As minio errors will 295 // have StatusCode to help to convert to object errors. 296 return err 297 } 298 299 switch minioErr.Code { 300 case "BucketAlreadyOwnedByYou": 301 err = BucketAlreadyOwnedByYou{} 302 case "BucketNotEmpty": 303 err = BucketNotEmpty{} 304 case "NoSuchBucketPolicy": 305 err = BucketPolicyNotFound{} 306 case "NoSuchLifecycleConfiguration": 307 err = BucketLifecycleNotFound{} 308 case "InvalidBucketName": 309 err = BucketNameInvalid{Bucket: bucket} 310 case "InvalidPart": 311 err = InvalidPart{} 312 case "NoSuchBucket": 313 err = BucketNotFound{Bucket: bucket} 314 case "NoSuchKey": 315 if object != "" { 316 err = ObjectNotFound{Bucket: bucket, Object: object} 317 } else { 318 err = BucketNotFound{Bucket: bucket} 319 } 320 case "XMinioInvalidObjectName": 321 err = ObjectNameInvalid{} 322 case "AccessDenied": 323 err = PrefixAccessDenied{ 324 Bucket: bucket, 325 Object: object, 326 } 327 case "XAmzContentSHA256Mismatch": 328 err = hash.SHA256Mismatch{} 329 case "NoSuchUpload": 330 err = InvalidUploadID{} 331 case "EntityTooSmall": 332 err = PartTooSmall{} 333 } 334 335 return err 336 } 337 338 // ComputeCompleteMultipartMD5 calculates MD5 ETag for complete multipart responses 339 func ComputeCompleteMultipartMD5(parts []CompletePart) string { 340 return getCompleteMultipartMD5(parts) 341 } 342 343 // parse gateway sse env variable 344 func parseGatewaySSE(s string) (gatewaySSE, error) { 345 l := strings.Split(s, ";") 346 var gwSlice gatewaySSE 347 for _, val := range l { 348 v := strings.ToUpper(val) 349 switch v { 350 case "": 351 continue 352 case gatewaySSES3: 353 fallthrough 354 case gatewaySSEC: 355 gwSlice = append(gwSlice, v) 356 continue 357 default: 358 return nil, config.ErrInvalidGWSSEValue(nil).Msg("gateway SSE cannot be (%s) ", v) 359 } 360 } 361 return gwSlice, nil 362 } 363 364 // handle gateway env vars 365 func gatewayHandleEnvVars() { 366 // Handle common env vars. 367 HandleCommonEnvVars() 368 369 if !globalActiveCred.IsValid() { 370 logger.Fatal(config.ErrInvalidCredentials(nil), 371 "Unable to validate credentials inherited from the shell environment") 372 } 373 374 gwsseVal := env.Get("MINIO_GATEWAY_SSE", "") 375 if gwsseVal != "" { 376 var err error 377 GlobalGatewaySSE, err = parseGatewaySSE(gwsseVal) 378 if err != nil { 379 logger.Fatal(err, "Unable to parse MINIO_GATEWAY_SSE value (`%s`)", gwsseVal) 380 } 381 } 382 } 383 384 // shouldMeterRequest checks whether incoming request should be added to prometheus gateway metrics 385 func shouldMeterRequest(req *http.Request) bool { 386 return !(guessIsBrowserReq(req) || guessIsHealthCheckReq(req) || guessIsMetricsReq(req)) 387 } 388 389 // MetricsTransport is a custom wrapper around Transport to track metrics 390 type MetricsTransport struct { 391 Transport *http.Transport 392 Metrics *BackendMetrics 393 } 394 395 // RoundTrip implements the RoundTrip method for MetricsTransport 396 func (m MetricsTransport) RoundTrip(r *http.Request) (*http.Response, error) { 397 metered := shouldMeterRequest(r) 398 if metered && (r.Method == http.MethodPost || r.Method == http.MethodPut) { 399 m.Metrics.IncRequests(r.Method) 400 if r.ContentLength > 0 { 401 m.Metrics.IncBytesSent(uint64(r.ContentLength)) 402 } 403 } 404 // Make the request to the server. 405 resp, err := m.Transport.RoundTrip(r) 406 if err != nil { 407 return nil, err 408 } 409 if metered && (r.Method == http.MethodGet || r.Method == http.MethodHead) { 410 m.Metrics.IncRequests(r.Method) 411 if resp.ContentLength > 0 { 412 m.Metrics.IncBytesReceived(uint64(resp.ContentLength)) 413 } 414 } 415 return resp, nil 416 }