github.com/rudderlabs/rudder-go-kit@v0.30.0/filemanager/miniomanager.go (about) 1 package filemanager 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/url" 8 "os" 9 "path" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/minio/minio-go/v7" 15 "github.com/minio/minio-go/v7/pkg/credentials" 16 17 "github.com/rudderlabs/rudder-go-kit/logger" 18 ) 19 20 type MinioConfig struct { 21 Bucket string 22 Prefix string 23 EndPoint string 24 AccessKeyID string 25 SecretAccessKey string 26 UseSSL bool 27 } 28 29 // NewMinioManager creates a new file manager for minio 30 func NewMinioManager(config map[string]interface{}, log logger.Logger, defaultTimeout func() time.Duration) (*minioManager, error) { 31 return &minioManager{ 32 baseManager: &baseManager{ 33 logger: log, 34 defaultTimeout: defaultTimeout, 35 }, 36 config: minioConfig(config), 37 }, nil 38 } 39 40 func (m *minioManager) ListFilesWithPrefix(ctx context.Context, startAfter, prefix string, maxItems int64) ListSession { 41 return &minioListSession{ 42 baseListSession: &baseListSession{ 43 ctx: ctx, 44 startAfter: startAfter, 45 prefix: prefix, 46 maxItems: maxItems, 47 }, 48 manager: m, 49 isTruncated: true, 50 } 51 } 52 53 func (m *minioManager) Download(ctx context.Context, file *os.File, key string) error { 54 minioClient, err := m.getClient() 55 if err != nil { 56 return err 57 } 58 59 ctx, cancel := context.WithTimeout(ctx, m.getTimeout()) 60 defer cancel() 61 62 err = minioClient.FGetObject(ctx, m.config.Bucket, key, file.Name(), minio.GetObjectOptions{}) 63 return err 64 } 65 66 func (m *minioManager) Upload(ctx context.Context, file *os.File, prefixes ...string) (UploadedFile, error) { 67 if m.config.Bucket == "" { 68 return UploadedFile{}, errors.New("no storage bucket configured to uploader") 69 } 70 71 minioClient, err := m.getClient() 72 if err != nil { 73 return UploadedFile{}, err 74 } 75 76 ctx, cancel := context.WithTimeout(ctx, m.getTimeout()) 77 defer cancel() 78 79 exists, err := minioClient.BucketExists(ctx, m.config.Bucket) 80 if err != nil { 81 return UploadedFile{}, fmt.Errorf("checking bucket: %w", err) 82 } 83 if !exists { 84 if err = minioClient.MakeBucket(ctx, m.config.Bucket, minio.MakeBucketOptions{Region: "us-east-1"}); err != nil { 85 return UploadedFile{}, fmt.Errorf("creating bucket: %w", err) 86 } 87 } 88 89 fileName := path.Join(m.config.Prefix, path.Join(prefixes...), path.Base(file.Name())) 90 91 _, err = minioClient.FPutObject(ctx, m.config.Bucket, fileName, file.Name(), minio.PutObjectOptions{}) 92 if err != nil { 93 return UploadedFile{}, err 94 } 95 96 return UploadedFile{Location: m.objectUrl(fileName), ObjectName: fileName}, nil 97 } 98 99 func (m *minioManager) Delete(ctx context.Context, keys []string) (err error) { 100 objectChannel := make(chan minio.ObjectInfo, len(keys)) 101 for _, key := range keys { 102 objectChannel <- minio.ObjectInfo{Key: key} 103 } 104 close(objectChannel) 105 106 minioClient, err := m.getClient() 107 if err != nil { 108 return err 109 } 110 111 ctx, cancel := context.WithTimeout(ctx, m.getTimeout()) 112 defer cancel() 113 114 tmp := <-minioClient.RemoveObjects(ctx, m.config.Bucket, objectChannel, minio.RemoveObjectsOptions{}) 115 return tmp.Err 116 } 117 118 func (m *minioManager) Prefix() string { 119 return m.config.Prefix 120 } 121 122 /* 123 GetObjectNameFromLocation gets the object name/key name from the object location url 124 125 https://minio-endpoint/bucket-name/key1 - >> key1 126 http://minio-endpoint/bucket-name/key2 - >> key2 127 */ 128 func (m *minioManager) GetObjectNameFromLocation(location string) (string, error) { 129 var baseURL string 130 if m.config.UseSSL { 131 baseURL += "https://" 132 } else { 133 baseURL += "http://" 134 } 135 baseURL += m.config.EndPoint + "/" 136 baseURL += m.config.Bucket + "/" 137 return location[len(baseURL):], nil 138 } 139 140 func (m *minioManager) GetDownloadKeyFromFileLocation(location string) string { 141 parsedUrl, err := url.Parse(location) 142 if err != nil { 143 fmt.Println("error while parsing location url: ", err) 144 } 145 trimedUrl := strings.TrimLeft(parsedUrl.Path, "/") 146 return strings.TrimPrefix(trimedUrl, fmt.Sprintf(`%s/`, m.config.Bucket)) 147 } 148 149 func (m *minioManager) objectUrl(objectName string) string { 150 protocol := "http" 151 if m.config.UseSSL { 152 protocol = "https" 153 } 154 return protocol + "://" + m.config.EndPoint + "/" + m.config.Bucket + "/" + objectName 155 } 156 157 func (m *minioManager) getClient() (*minio.Client, error) { 158 m.clientOnce.Do(func() { 159 m.client, m.clientErr = minio.New(m.config.EndPoint, &minio.Options{ 160 Creds: credentials.NewStaticV4(m.config.AccessKeyID, m.config.SecretAccessKey, ""), 161 Secure: m.config.UseSSL, 162 }) 163 if m.clientErr != nil { 164 m.client = &minio.Client{} 165 } 166 }) 167 168 return m.client, m.clientErr 169 } 170 171 type minioManager struct { 172 *baseManager 173 config *MinioConfig 174 175 client *minio.Client 176 clientErr error 177 clientOnce sync.Once 178 } 179 180 func minioConfig(config map[string]interface{}) *MinioConfig { 181 var bucketName, prefix, endPoint, accessKeyID, secretAccessKey string 182 var useSSL, ok bool 183 if config["bucketName"] != nil { 184 tmp, ok := config["bucketName"].(string) 185 if ok { 186 bucketName = tmp 187 } 188 } 189 if config["prefix"] != nil { 190 tmp, ok := config["prefix"].(string) 191 if ok { 192 prefix = tmp 193 } 194 } 195 if config["endPoint"] != nil { 196 tmp, ok := config["endPoint"].(string) 197 if ok { 198 endPoint = tmp 199 } 200 } 201 if config["accessKeyID"] != nil { 202 tmp, ok := config["accessKeyID"].(string) 203 if ok { 204 accessKeyID = tmp 205 } 206 } 207 if config["secretAccessKey"] != nil { 208 tmp, ok := config["secretAccessKey"].(string) 209 if ok { 210 secretAccessKey = tmp 211 } 212 } 213 if config["useSSL"] != nil { 214 if useSSL, ok = config["useSSL"].(bool); !ok { 215 useSSL = false 216 } 217 } 218 219 return &MinioConfig{ 220 Bucket: bucketName, 221 Prefix: prefix, 222 EndPoint: endPoint, 223 AccessKeyID: accessKeyID, 224 SecretAccessKey: secretAccessKey, 225 UseSSL: useSSL, 226 } 227 } 228 229 type minioListSession struct { 230 *baseListSession 231 manager *minioManager 232 233 continuationToken string 234 isTruncated bool 235 } 236 237 func (l *minioListSession) Next() (fileObjects []*FileInfo, err error) { 238 manager := l.manager 239 if !l.isTruncated { 240 manager.logger.Infof("Manager is truncated: %v so returning here", l.isTruncated) 241 return 242 } 243 fileObjects = make([]*FileInfo, 0) 244 245 // Created minio core 246 core, err := minio.NewCore(manager.config.EndPoint, &minio.Options{ 247 Creds: credentials.NewStaticV4(manager.config.AccessKeyID, manager.config.SecretAccessKey, ""), 248 Secure: manager.config.UseSSL, 249 }) 250 if err != nil { 251 return 252 } 253 254 // List the Objects in the bucket 255 result, err := core.ListObjectsV2(manager.config.Bucket, l.prefix, l.startAfter, l.continuationToken, "", int(l.maxItems)) 256 if err != nil { 257 return 258 } 259 260 for idx := range result.Contents { 261 fileObjects = append(fileObjects, &FileInfo{result.Contents[idx].Key, result.Contents[idx].LastModified}) 262 } 263 l.isTruncated = result.IsTruncated 264 l.continuationToken = result.NextContinuationToken 265 return 266 }