github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/warm-backend-s3.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "io" 25 "net/http" 26 "net/url" 27 "strings" 28 "sync" 29 "time" 30 31 "github.com/minio/madmin-go/v3" 32 "github.com/minio/minio-go/v7" 33 "github.com/minio/minio-go/v7/pkg/credentials" 34 ) 35 36 // getRemoteTierTargetInstanceTransport contains a singleton roundtripper. 37 var ( 38 getRemoteTierTargetInstanceTransport http.RoundTripper 39 getRemoteTierTargetInstanceTransportOnce sync.Once 40 ) 41 42 type warmBackendS3 struct { 43 client *minio.Client 44 core *minio.Core 45 Bucket string 46 Prefix string 47 StorageClass string 48 } 49 50 func (s3 *warmBackendS3) ToObjectError(err error, params ...string) error { 51 object := "" 52 if len(params) >= 1 { 53 object = params[0] 54 } 55 56 return ErrorRespToObjectError(err, s3.Bucket, s3.getDest(object)) 57 } 58 59 func (s3 *warmBackendS3) getDest(object string) string { 60 destObj := object 61 if s3.Prefix != "" { 62 destObj = fmt.Sprintf("%s/%s", s3.Prefix, object) 63 } 64 return destObj 65 } 66 67 func (s3 *warmBackendS3) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) { 68 res, err := s3.client.PutObject(ctx, s3.Bucket, s3.getDest(object), r, length, minio.PutObjectOptions{ 69 SendContentMd5: true, 70 StorageClass: s3.StorageClass, 71 }) 72 return remoteVersionID(res.VersionID), s3.ToObjectError(err, object) 73 } 74 75 func (s3 *warmBackendS3) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error) { 76 gopts := minio.GetObjectOptions{} 77 78 if rv != "" { 79 gopts.VersionID = string(rv) 80 } 81 if opts.startOffset >= 0 && opts.length > 0 { 82 if err := gopts.SetRange(opts.startOffset, opts.startOffset+opts.length-1); err != nil { 83 return nil, s3.ToObjectError(err, object) 84 } 85 } 86 c := &minio.Core{Client: s3.client} 87 // Important to use core primitives here to pass range get options as is. 88 r, _, _, err := c.GetObject(ctx, s3.Bucket, s3.getDest(object), gopts) 89 if err != nil { 90 return nil, s3.ToObjectError(err, object) 91 } 92 return r, nil 93 } 94 95 func (s3 *warmBackendS3) Remove(ctx context.Context, object string, rv remoteVersionID) error { 96 ropts := minio.RemoveObjectOptions{} 97 if rv != "" { 98 ropts.VersionID = string(rv) 99 } 100 err := s3.client.RemoveObject(ctx, s3.Bucket, s3.getDest(object), ropts) 101 return s3.ToObjectError(err, object) 102 } 103 104 func (s3 *warmBackendS3) InUse(ctx context.Context) (bool, error) { 105 result, err := s3.core.ListObjectsV2(s3.Bucket, s3.Prefix, "", "", slashSeparator, 1) 106 if err != nil { 107 return false, s3.ToObjectError(err) 108 } 109 return len(result.CommonPrefixes) > 0 || len(result.Contents) > 0, nil 110 } 111 112 func newWarmBackendS3(conf madmin.TierS3, tier string) (*warmBackendS3, error) { 113 u, err := url.Parse(conf.Endpoint) 114 if err != nil { 115 return nil, err 116 } 117 118 // Validation code 119 switch { 120 case conf.AWSRoleWebIdentityTokenFile == "" && conf.AWSRoleARN != "" || conf.AWSRoleWebIdentityTokenFile != "" && conf.AWSRoleARN == "": 121 return nil, errors.New("both the token file and the role ARN are required") 122 case conf.AccessKey == "" && conf.SecretKey != "" || conf.AccessKey != "" && conf.SecretKey == "": 123 return nil, errors.New("both the access and secret keys are required") 124 case conf.AWSRole && (conf.AWSRoleWebIdentityTokenFile != "" || conf.AWSRoleARN != "" || conf.AccessKey != "" || conf.SecretKey != ""): 125 return nil, errors.New("AWS Role cannot be activated with static credentials or the web identity token file") 126 case conf.Bucket == "": 127 return nil, errors.New("no bucket name was provided") 128 } 129 130 // Credentials initialization 131 var creds *credentials.Credentials 132 switch { 133 case conf.AWSRole: 134 creds = credentials.New(&credentials.IAM{ 135 Client: &http.Client{ 136 Transport: NewHTTPTransport(), 137 }, 138 }) 139 case conf.AWSRoleWebIdentityTokenFile != "" && conf.AWSRoleARN != "": 140 sessionName := conf.AWSRoleSessionName 141 if sessionName == "" { 142 // RoleSessionName has a limited set of characters (https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) 143 sessionName = "minio-tier-" + mustGetUUID() 144 } 145 s3WebIdentityIAM := credentials.IAM{ 146 Client: &http.Client{ 147 Transport: NewHTTPTransport(), 148 }, 149 EKSIdentity: struct { 150 TokenFile string 151 RoleARN string 152 RoleSessionName string 153 }{ 154 conf.AWSRoleWebIdentityTokenFile, 155 conf.AWSRoleARN, 156 sessionName, 157 }, 158 } 159 creds = credentials.New(&s3WebIdentityIAM) 160 case conf.AccessKey != "" && conf.SecretKey != "": 161 creds = credentials.NewStaticV4(conf.AccessKey, conf.SecretKey, "") 162 default: 163 return nil, errors.New("insufficient parameters for S3 backend authentication") 164 } 165 getRemoteTierTargetInstanceTransportOnce.Do(func() { 166 getRemoteTierTargetInstanceTransport = NewHTTPTransportWithTimeout(10 * time.Minute) 167 }) 168 opts := &minio.Options{ 169 Creds: creds, 170 Secure: u.Scheme == "https", 171 Transport: getRemoteTierTargetInstanceTransport, 172 } 173 client, err := minio.New(u.Host, opts) 174 if err != nil { 175 return nil, err 176 } 177 client.SetAppInfo(fmt.Sprintf("s3-tier-%s", tier), ReleaseTag) 178 179 core := &minio.Core{Client: client} 180 return &warmBackendS3{ 181 client: client, 182 core: core, 183 Bucket: conf.Bucket, 184 Prefix: strings.TrimSuffix(conf.Prefix, slashSeparator), 185 StorageClass: conf.StorageClass, 186 }, nil 187 }