Skip to content

Commit

Permalink
feat: Add S3 ListDirectory operation (#34)
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Behar <simbeh7@gmail.com>
  • Loading branch information
simster7 authored Feb 25, 2021
1 parent ca11202 commit 7a9a483
Showing 1 changed file with 45 additions and 25 deletions.
70 changes: 45 additions & 25 deletions s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type S3Client interface {
// GetDirectory downloads a directory to a local file path
GetDirectory(bucket, key, path string) error

// ListDirectory list the contents of a directory/bucket
ListDirectory(bucket, keyPrefix string) ([]string, error)

// IsDirectory tests if the key is acting like a s3 directory
IsDirectory(bucket, key string) (bool, error)

Expand Down Expand Up @@ -60,6 +63,8 @@ type s3client struct {
ctx context.Context
}

var _ S3Client = &s3client{}

// Get AWS credentials based on default order from aws SDK
func GetAWSCredentials(opts S3ClientOpts) (*credentials.Credentials, error) {
sess := session.Must(session.NewSessionWithOptions(session.Options{
Expand Down Expand Up @@ -213,34 +218,16 @@ func (s *s3client) GetFile(bucket, key, path string) error {
// GetDirectory downloads a s3 directory to a local path
func (s *s3client) GetDirectory(bucket, keyPrefix, path string) error {
log.Infof("Getting directory from s3 (endpoint: %s, bucket: %s, key: %s) to %s", s.Endpoint, bucket, keyPrefix, path)
keyPrefix = filepath.Clean(keyPrefix) + "/"
if os.PathSeparator == '\\' {
keyPrefix = strings.ReplaceAll(keyPrefix, "\\", "/")
}

doneCh := make(chan struct{})
defer close(doneCh)
listOpts := minio.ListObjectsOptions{
Prefix: keyPrefix,
Recursive: true,
keys, err := s.ListDirectory(bucket, keyPrefix)
if err != nil {
return err
}
objCh := s.minioClient.ListObjects(s.ctx, bucket, listOpts)
for obj := range objCh {
if obj.Err != nil {
return errors.WithStack(obj.Err)
}
if strings.HasSuffix(obj.Key, "/") {
// When a dir is created through AWS S3 console, a nameless obj will be created
// automatically, its key will be {dir_name} + "/". This obj does not display in the
// console, but you can see it when using aws cli.
// If obj.Key ends with "/" means it's a dir obj, we need to skip it, otherwise it
// will be downloaded as a regular file with the same name as the dir, and it will
// creates error when downloading the files under the dir.
continue
}
relKeyPath := strings.TrimPrefix(obj.Key, keyPrefix)

for _, objKey := range keys {
relKeyPath := strings.TrimPrefix(objKey, keyPrefix)
localPath := filepath.Join(path, relKeyPath)
err := s.minioClient.FGetObject(s.ctx, bucket, obj.Key, localPath, minio.GetObjectOptions{})
err := s.minioClient.FGetObject(s.ctx, bucket, objKey, localPath, minio.GetObjectOptions{})
if err != nil {
return errors.WithStack(err)
}
Expand Down Expand Up @@ -268,6 +255,39 @@ func (s *s3client) IsDirectory(bucket, key string) (bool, error) {
return false, nil
}

func (s *s3client) ListDirectory(bucket, keyPrefix string) ([]string, error) {
log.Infof("Listing directory from s3 (endpoint: %s, bucket: %s, key: %s)", s.Endpoint, bucket, keyPrefix)
keyPrefix = filepath.Clean(keyPrefix) + "/"
if os.PathSeparator == '\\' {
keyPrefix = strings.ReplaceAll(keyPrefix, "\\", "/")
}

doneCh := make(chan struct{})
defer close(doneCh)
listOpts := minio.ListObjectsOptions{
//Prefix: keyPrefix,
Recursive: true,
}
var out []string
objCh := s.minioClient.ListObjects(s.ctx, bucket, listOpts)
for obj := range objCh {
if obj.Err != nil {
return nil, errors.WithStack(obj.Err)
}
if strings.HasSuffix(obj.Key, "/") {
// When a dir is created through AWS S3 console, a nameless obj will be created
// automatically, its key will be {dir_name} + "/". This obj does not display in the
// console, but you can see it when using aws cli.
// If obj.Key ends with "/" means it's a dir obj, we need to skip it, otherwise it
// will be downloaded as a regular file with the same name as the dir, and it will
// creates error when downloading the files under the dir.
continue
}
out = append(out, obj.Key)
}
return out, nil
}

// IsS3ErrCode returns if the supplied error is of a specific S3 error code
func IsS3ErrCode(err error, code string) bool {
err = errors.Cause(err)
Expand Down

0 comments on commit 7a9a483

Please sign in to comment.