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  }