github.com/weaviate/weaviate@v1.24.6/modules/backup-s3/client.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package modstgs3 13 14 import ( 15 "bytes" 16 "context" 17 "fmt" 18 "io" 19 "net/http" 20 "os" 21 "path" 22 23 "github.com/minio/minio-go/v7" 24 "github.com/minio/minio-go/v7/pkg/credentials" 25 "github.com/pkg/errors" 26 "github.com/sirupsen/logrus" 27 "github.com/weaviate/weaviate/entities/backup" 28 "github.com/weaviate/weaviate/usecases/monitoring" 29 ) 30 31 type s3Client struct { 32 client *minio.Client 33 config *clientConfig 34 logger logrus.FieldLogger 35 dataPath string 36 } 37 38 func newClient(config *clientConfig, logger logrus.FieldLogger, dataPath string) (*s3Client, error) { 39 region := os.Getenv("AWS_REGION") 40 if len(region) == 0 { 41 region = os.Getenv("AWS_DEFAULT_REGION") 42 } 43 44 var creds *credentials.Credentials 45 if (os.Getenv("AWS_ACCESS_KEY_ID") != "" || os.Getenv("AWS_ACCESS_KEY") != "") && 46 (os.Getenv("AWS_SECRET_ACCESS_KEY") != "" || os.Getenv("AWS_SECRET_KEY") != "") { 47 creds = credentials.NewEnvAWS() 48 } else { 49 creds = credentials.NewIAM("") 50 if _, err := creds.Get(); err != nil { 51 // can be anonymous access 52 creds = credentials.NewEnvAWS() 53 } 54 } 55 56 client, err := minio.New(config.Endpoint, &minio.Options{ 57 Creds: creds, 58 Region: region, 59 Secure: config.UseSSL, 60 }) 61 if err != nil { 62 return nil, errors.Wrap(err, "create client") 63 } 64 return &s3Client{client, config, logger, dataPath}, nil 65 } 66 67 func (s *s3Client) makeObjectName(parts ...string) string { 68 base := path.Join(parts...) 69 return path.Join(s.config.BackupPath, base) 70 } 71 72 func (s *s3Client) HomeDir(backupID string) string { 73 return "s3://" + path.Join(s.config.Bucket, 74 s.makeObjectName(backupID)) 75 } 76 77 func (s *s3Client) GetObject(ctx context.Context, backupID, key string) ([]byte, error) { 78 objectName := s.makeObjectName(backupID, key) 79 80 if err := ctx.Err(); err != nil { 81 return nil, backup.NewErrContextExpired(errors.Wrapf(err, "get object '%s'", objectName)) 82 } 83 84 obj, err := s.client.GetObject(ctx, s.config.Bucket, objectName, minio.GetObjectOptions{}) 85 if err != nil { 86 return nil, backup.NewErrInternal(errors.Wrapf(err, "get object '%s'", objectName)) 87 } 88 89 contents, err := io.ReadAll(obj) 90 if err != nil { 91 if s3Err, ok := err.(minio.ErrorResponse); ok && s3Err.StatusCode == http.StatusNotFound { 92 return nil, backup.NewErrNotFound(errors.Wrapf(err, "get object '%s'", objectName)) 93 } 94 return nil, backup.NewErrInternal(errors.Wrapf(err, "get object '%s'", objectName)) 95 } 96 97 metric, err := monitoring.GetMetrics().BackupRestoreDataTransferred.GetMetricWithLabelValues(Name, "class") 98 if err == nil { 99 metric.Add(float64(len(contents))) 100 } 101 102 return contents, nil 103 } 104 105 func (s *s3Client) PutFile(ctx context.Context, backupID, key string, srcPath string) error { 106 objectName := s.makeObjectName(backupID, key) 107 srcPath = path.Join(s.dataPath, srcPath) 108 opt := minio.PutObjectOptions{ContentType: "application/octet-stream"} 109 110 _, err := s.client.FPutObject(ctx, s.config.Bucket, objectName, srcPath, opt) 111 if err != nil { 112 return backup.NewErrInternal( 113 errors.Wrapf(err, "put file '%s'", objectName)) 114 } 115 116 // Get filesize 117 file, err := os.Stat(srcPath) 118 if err != nil { 119 return nil 120 } 121 size := file.Size() 122 123 metric, err := monitoring.GetMetrics().BackupStoreDataTransferred.GetMetricWithLabelValues(Name, "class") 124 if err == nil { 125 metric.Add(float64(size)) 126 } 127 return nil 128 } 129 130 func (s *s3Client) PutObject(ctx context.Context, backupID, key string, byes []byte) error { 131 objectName := s.makeObjectName(backupID, key) 132 opt := minio.PutObjectOptions{ContentType: "application/octet-stream"} 133 reader := bytes.NewReader(byes) 134 objectSize := int64(len(byes)) 135 136 _, err := s.client.PutObject(ctx, s.config.Bucket, objectName, reader, objectSize, opt) 137 if err != nil { 138 return backup.NewErrInternal( 139 errors.Wrapf(err, "put object '%s'", objectName)) 140 } 141 142 metric, err := monitoring.GetMetrics().BackupStoreDataTransferred.GetMetricWithLabelValues(Name, "class") 143 if err == nil { 144 metric.Add(float64(len(byes))) 145 } 146 return nil 147 } 148 149 func (s *s3Client) Initialize(ctx context.Context, backupID string) error { 150 key := "access-check" 151 152 if err := s.PutObject(ctx, backupID, key, []byte("")); err != nil { 153 return errors.Wrap(err, "failed to access-check s3 backup module") 154 } 155 156 objectName := s.makeObjectName(backupID, key) 157 opt := minio.RemoveObjectOptions{} 158 if err := s.client.RemoveObject(ctx, s.config.Bucket, objectName, opt); err != nil { 159 return errors.Wrap(err, "failed to remove access-check s3 backup module") 160 } 161 162 return nil 163 } 164 165 // WriteFile downloads contents of an object to a local file destPath 166 func (s *s3Client) WriteToFile(ctx context.Context, backupID, key, destPath string) error { 167 object := s.makeObjectName(backupID, key) 168 err := s.client.FGetObject(ctx, s.config.Bucket, object, destPath, minio.GetObjectOptions{}) 169 if err != nil { 170 return fmt.Errorf("s3.FGetObject %q %q: %w", destPath, object, err) 171 } 172 173 if st, err := os.Stat(destPath); err == nil { 174 metric, err := monitoring.GetMetrics().BackupRestoreDataTransferred.GetMetricWithLabelValues(Name, "class") 175 if err == nil { 176 metric.Add(float64(st.Size())) 177 } 178 } 179 return nil 180 } 181 182 func (s *s3Client) Write(ctx context.Context, backupID, key string, r io.ReadCloser) (int64, error) { 183 defer r.Close() 184 path := s.makeObjectName(backupID, key) 185 opt := minio.PutObjectOptions{ 186 ContentType: "application/octet-stream", 187 DisableMultipart: false, 188 } 189 190 info, err := s.client.PutObject(ctx, s.config.Bucket, path, r, -1, opt) 191 if err != nil { 192 return info.Size, fmt.Errorf("write object %q", path) 193 } 194 195 if metric, err := monitoring.GetMetrics().BackupStoreDataTransferred. 196 GetMetricWithLabelValues(Name, "class"); err == nil { 197 metric.Add(float64(float64(info.Size))) 198 } 199 return info.Size, nil 200 } 201 202 func (s *s3Client) Read(ctx context.Context, backupID, key string, w io.WriteCloser) (int64, error) { 203 defer w.Close() 204 path := s.makeObjectName(backupID, key) 205 obj, err := s.client.GetObject(ctx, s.config.Bucket, path, minio.GetObjectOptions{}) 206 if err != nil { 207 return 0, fmt.Errorf("get object %q: %w", path, err) 208 } 209 210 read, err := io.Copy(w, obj) 211 if err != nil { 212 err = fmt.Errorf("get object %q: %w", path, err) 213 if s3Err, ok := err.(minio.ErrorResponse); ok && s3Err.StatusCode == http.StatusNotFound { 214 err = backup.NewErrNotFound(err) 215 } 216 return 0, err 217 } 218 219 if metric, err := monitoring.GetMetrics().BackupRestoreDataTransferred. 220 GetMetricWithLabelValues(Name, "class"); err == nil { 221 metric.Add(float64(float64(read))) 222 } 223 224 return read, nil 225 } 226 227 func (s *s3Client) SourceDataPath() string { 228 return s.dataPath 229 }