github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/amazon_s3.go (about)

     1  package reader
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net/url"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/Jeffail/benthos/v3/lib/log"
    16  	"github.com/Jeffail/benthos/v3/lib/message"
    17  	"github.com/Jeffail/benthos/v3/lib/metrics"
    18  	"github.com/Jeffail/benthos/v3/lib/types"
    19  	sess "github.com/Jeffail/benthos/v3/lib/util/aws/session"
    20  	"github.com/Jeffail/gabs/v2"
    21  	"github.com/aws/aws-sdk-go/aws"
    22  	"github.com/aws/aws-sdk-go/aws/session"
    23  	"github.com/aws/aws-sdk-go/service/s3"
    24  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    25  	"github.com/aws/aws-sdk-go/service/sqs"
    26  )
    27  
    28  //------------------------------------------------------------------------------
    29  
    30  // S3DownloadManagerConfig is a config struct containing fields for an S3
    31  // download manager.
    32  type S3DownloadManagerConfig struct {
    33  	Enabled bool `json:"enabled" yaml:"enabled"`
    34  }
    35  
    36  // AmazonS3Config contains configuration values for the AmazonS3 input type.
    37  type AmazonS3Config struct {
    38  	sess.Config        `json:",inline" yaml:",inline"`
    39  	Bucket             string                  `json:"bucket" yaml:"bucket"`
    40  	Prefix             string                  `json:"prefix" yaml:"prefix"`
    41  	Retries            int                     `json:"retries" yaml:"retries"`
    42  	ForcePathStyleURLs bool                    `json:"force_path_style_urls" yaml:"force_path_style_urls"`
    43  	DownloadManager    S3DownloadManagerConfig `json:"download_manager" yaml:"download_manager"`
    44  	DeleteObjects      bool                    `json:"delete_objects" yaml:"delete_objects"`
    45  	SQSURL             string                  `json:"sqs_url" yaml:"sqs_url"`
    46  	SQSEndpoint        string                  `json:"sqs_endpoint" yaml:"sqs_endpoint"`
    47  	SQSBodyPath        string                  `json:"sqs_body_path" yaml:"sqs_body_path"`
    48  	SQSBucketPath      string                  `json:"sqs_bucket_path" yaml:"sqs_bucket_path"`
    49  	SQSEnvelopePath    string                  `json:"sqs_envelope_path" yaml:"sqs_envelope_path"`
    50  	SQSMaxMessages     int64                   `json:"sqs_max_messages" yaml:"sqs_max_messages"`
    51  	MaxBatchCount      int                     `json:"max_batch_count" yaml:"max_batch_count"`
    52  	Timeout            string                  `json:"timeout" yaml:"timeout"`
    53  }
    54  
    55  // NewAmazonS3Config creates a new AmazonS3Config with default values.
    56  func NewAmazonS3Config() AmazonS3Config {
    57  	return AmazonS3Config{
    58  		Config:             sess.NewConfig(),
    59  		Bucket:             "",
    60  		Prefix:             "",
    61  		Retries:            3,
    62  		ForcePathStyleURLs: false,
    63  		DownloadManager: S3DownloadManagerConfig{
    64  			Enabled: true,
    65  		},
    66  		DeleteObjects:   false,
    67  		SQSURL:          "",
    68  		SQSEndpoint:     "",
    69  		SQSBodyPath:     "Records.*.s3.object.key",
    70  		SQSBucketPath:   "",
    71  		SQSEnvelopePath: "",
    72  		SQSMaxMessages:  10,
    73  		MaxBatchCount:   1,
    74  		Timeout:         "5s",
    75  	}
    76  }
    77  
    78  //------------------------------------------------------------------------------
    79  
    80  type objKey struct {
    81  	s3Key     string
    82  	s3Bucket  string
    83  	attempts  int
    84  	sqsHandle *sqs.DeleteMessageBatchRequestEntry
    85  }
    86  
    87  // AmazonS3 is a benthos reader.Type implementation that reads messages from an
    88  // Amazon S3 bucket.
    89  type AmazonS3 struct {
    90  	conf AmazonS3Config
    91  
    92  	sqsBodyPath   string
    93  	sqsEnvPath    string
    94  	sqsBucketPath string
    95  
    96  	readKeys      []objKey
    97  	targetKeys    []objKey
    98  	targetKeysMut sync.Mutex
    99  
   100  	readMethod func() (types.Part, objKey, error)
   101  
   102  	session    *session.Session
   103  	s3         *s3.S3
   104  	downloader *s3manager.Downloader
   105  	sqs        *sqs.SQS
   106  	timeout    time.Duration
   107  
   108  	log   log.Modular
   109  	stats metrics.Type
   110  }
   111  
   112  // NewAmazonS3 creates a new Amazon S3 bucket reader.Type.
   113  func NewAmazonS3(
   114  	conf AmazonS3Config,
   115  	log log.Modular,
   116  	stats metrics.Type,
   117  ) (*AmazonS3, error) {
   118  	if len(conf.SQSURL) > 0 && conf.SQSBodyPath == "Records.s3.object.key" {
   119  		log.Warnf("It looks like a deprecated SQS Body path is configured: 'Records.s3.object.key', you might not receive S3 items unless you update to the new syntax 'Records.*.s3.object.key'")
   120  	}
   121  
   122  	if conf.Bucket == "" {
   123  		return nil, errors.New("a bucket must be specified (even with an SQS bucket path configured)")
   124  	}
   125  
   126  	var timeout time.Duration
   127  	if tout := conf.Timeout; len(tout) > 0 {
   128  		var err error
   129  		if timeout, err = time.ParseDuration(tout); err != nil {
   130  			return nil, fmt.Errorf("failed to parse timeout string: %v", err)
   131  		}
   132  	}
   133  	if conf.MaxBatchCount < 1 {
   134  		return nil, fmt.Errorf("max_batch_count '%v' must be > 0", conf.MaxBatchCount)
   135  	}
   136  	s := &AmazonS3{
   137  		conf:          conf,
   138  		sqsBodyPath:   conf.SQSBodyPath,
   139  		sqsEnvPath:    conf.SQSEnvelopePath,
   140  		sqsBucketPath: conf.SQSBucketPath,
   141  		log:           log,
   142  		stats:         stats,
   143  		timeout:       timeout,
   144  	}
   145  	if conf.DownloadManager.Enabled {
   146  		s.readMethod = s.readFromMgr
   147  	} else {
   148  		s.readMethod = s.read
   149  	}
   150  	return s, nil
   151  }
   152  
   153  // Connect attempts to establish a connection to the target S3 bucket and any
   154  // relevant queues used to traverse the objects (SQS, etc).
   155  func (a *AmazonS3) Connect() error {
   156  	return a.ConnectWithContext(context.Background())
   157  }
   158  
   159  // ConnectWithContext attempts to establish a connection to the target S3 bucket
   160  // and any relevant queues used to traverse the objects (SQS, etc).
   161  func (a *AmazonS3) ConnectWithContext(ctx context.Context) error {
   162  	a.targetKeysMut.Lock()
   163  	defer a.targetKeysMut.Unlock()
   164  
   165  	if a.session != nil {
   166  		return nil
   167  	}
   168  
   169  	sess, err := a.conf.GetSession(func(c *aws.Config) {
   170  		c.S3ForcePathStyle = aws.Bool(a.conf.ForcePathStyleURLs)
   171  	})
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	sThree := s3.New(sess)
   177  	dler := s3manager.NewDownloader(sess)
   178  
   179  	if a.conf.SQSURL == "" {
   180  		listInput := &s3.ListObjectsInput{
   181  			Bucket: aws.String(a.conf.Bucket),
   182  		}
   183  		if len(a.conf.Prefix) > 0 {
   184  			listInput.Prefix = aws.String(a.conf.Prefix)
   185  		}
   186  		err := sThree.ListObjectsPagesWithContext(ctx, listInput,
   187  			func(page *s3.ListObjectsOutput, isLastPage bool) bool {
   188  				for _, obj := range page.Contents {
   189  					a.targetKeys = append(a.targetKeys, objKey{
   190  						s3Key:    *obj.Key,
   191  						attempts: a.conf.Retries,
   192  					})
   193  				}
   194  				return true
   195  			},
   196  		)
   197  		if err != nil {
   198  			return fmt.Errorf("failed to list objects: %v", err)
   199  		}
   200  	} else {
   201  		sqsSess := sess.Copy()
   202  		if len(a.conf.SQSEndpoint) > 0 {
   203  			sqsSess.Config.Endpoint = &a.conf.SQSEndpoint
   204  		}
   205  		a.sqs = sqs.New(sqsSess)
   206  	}
   207  
   208  	a.log.Infof("Receiving Amazon S3 objects from bucket: %s\n", a.conf.Bucket)
   209  
   210  	a.session = sess
   211  	a.downloader = dler
   212  	a.s3 = sThree
   213  	return nil
   214  }
   215  
   216  func digStrsFromSlices(slice []interface{}) []string {
   217  	var strs []string
   218  	for _, v := range slice {
   219  		switch t := v.(type) {
   220  		case []interface{}:
   221  			strs = append(strs, digStrsFromSlices(t)...)
   222  		case string:
   223  			strs = append(strs, t)
   224  		}
   225  	}
   226  	return strs
   227  }
   228  
   229  type objTarget struct {
   230  	key    string
   231  	bucket string
   232  }
   233  
   234  func (a *AmazonS3) parseItemPaths(sqsMsg *string) ([]objTarget, error) {
   235  	gObj, err := gabs.ParseJSON([]byte(*sqsMsg))
   236  	if err != nil {
   237  		return nil, fmt.Errorf("failed to parse SQS message: %v", err)
   238  	}
   239  
   240  	if len(a.sqsEnvPath) > 0 {
   241  		switch t := gObj.Path(a.sqsEnvPath).Data().(type) {
   242  		case string:
   243  			if gObj, err = gabs.ParseJSON([]byte(t)); err != nil {
   244  				return nil, fmt.Errorf("failed to parse SQS message envelope: %v", err)
   245  			}
   246  		case []interface{}:
   247  			docs := []interface{}{}
   248  			strs := digStrsFromSlices(t)
   249  			for _, v := range strs {
   250  				var gObj2 interface{}
   251  				if err2 := json.Unmarshal([]byte(v), &gObj2); err2 == nil {
   252  					docs = append(docs, gObj2)
   253  				}
   254  			}
   255  			if len(docs) == 0 {
   256  				return nil, errors.New("couldn't locate S3 items from SQS message")
   257  			}
   258  			gObj = gabs.Wrap(docs)
   259  		default:
   260  			return nil, fmt.Errorf("unexpected envelope value: %v", t)
   261  		}
   262  	}
   263  
   264  	var buckets []string
   265  	switch t := gObj.Path(a.sqsBucketPath).Data().(type) {
   266  	case string:
   267  		buckets = []string{t}
   268  	case []interface{}:
   269  		buckets = digStrsFromSlices(t)
   270  	}
   271  
   272  	items := []objTarget{}
   273  
   274  	switch t := gObj.Path(a.sqsBodyPath).Data().(type) {
   275  	case string:
   276  		if strings.HasPrefix(t, a.conf.Prefix) {
   277  			bucket := ""
   278  			if len(buckets) > 0 {
   279  				bucket = buckets[0]
   280  			}
   281  			items = append(items, objTarget{
   282  				key:    t,
   283  				bucket: bucket,
   284  			})
   285  		}
   286  	case []interface{}:
   287  		newTargets := []string{}
   288  		strs := digStrsFromSlices(t)
   289  		for _, p := range strs {
   290  			if strings.HasPrefix(p, a.conf.Prefix) {
   291  				newTargets = append(newTargets, p)
   292  			}
   293  		}
   294  		if len(newTargets) > 0 {
   295  			for i, target := range newTargets {
   296  				bucket := ""
   297  				if len(buckets) > i {
   298  					bucket = buckets[i]
   299  				}
   300  				decodedTarget, err := url.QueryUnescape(target)
   301  				if err != nil {
   302  					return nil, fmt.Errorf("failed to decode S3 path: %v", err)
   303  				}
   304  				items = append(items, objTarget{
   305  					key:    decodedTarget,
   306  					bucket: bucket,
   307  				})
   308  			}
   309  		} else {
   310  			return nil, errors.New("no items found in SQS message at specified path")
   311  		}
   312  	default:
   313  		return nil, errors.New("no items found in SQS message at specified path")
   314  	}
   315  	return items, nil
   316  }
   317  
   318  func (a *AmazonS3) rejectObjects(keys []objKey) {
   319  	ctx, done := context.WithTimeout(context.Background(), a.timeout)
   320  	defer done()
   321  
   322  	var failedMessageHandles []*sqs.ChangeMessageVisibilityBatchRequestEntry
   323  	for _, key := range keys {
   324  		failedMessageHandles = append(failedMessageHandles, &sqs.ChangeMessageVisibilityBatchRequestEntry{
   325  			Id:                key.sqsHandle.Id,
   326  			ReceiptHandle:     key.sqsHandle.ReceiptHandle,
   327  			VisibilityTimeout: aws.Int64(0),
   328  		})
   329  	}
   330  	for len(failedMessageHandles) > 0 {
   331  		input := sqs.ChangeMessageVisibilityBatchInput{
   332  			QueueUrl: aws.String(a.conf.SQSURL),
   333  			Entries:  failedMessageHandles,
   334  		}
   335  
   336  		// trim input entries to max size
   337  		if len(failedMessageHandles) > 10 {
   338  			input.Entries, failedMessageHandles = failedMessageHandles[:10], failedMessageHandles[10:]
   339  		} else {
   340  			failedMessageHandles = nil
   341  		}
   342  		if _, err := a.sqs.ChangeMessageVisibilityBatchWithContext(ctx, &input); err != nil {
   343  			a.log.Errorf("Failed to reject SQS message: %v\n", err)
   344  		}
   345  	}
   346  }
   347  
   348  func (a *AmazonS3) deleteObjects(keys []objKey) {
   349  	ctx, done := context.WithTimeout(context.Background(), a.timeout)
   350  	defer done()
   351  
   352  	deleteHandles := []*sqs.DeleteMessageBatchRequestEntry{}
   353  	for _, key := range keys {
   354  		if a.conf.DeleteObjects {
   355  			bucket := a.conf.Bucket
   356  			if len(key.s3Bucket) > 0 {
   357  				bucket = key.s3Bucket
   358  			}
   359  			if _, serr := a.s3.DeleteObjectWithContext(ctx, &s3.DeleteObjectInput{
   360  				Bucket: aws.String(bucket),
   361  				Key:    aws.String(key.s3Key),
   362  			}); serr != nil {
   363  				a.log.Errorf("Failed to delete consumed object: %v\n", serr)
   364  			}
   365  		}
   366  		if key.sqsHandle != nil {
   367  			deleteHandles = append(deleteHandles, key.sqsHandle)
   368  		}
   369  	}
   370  	for len(deleteHandles) > 0 {
   371  		input := sqs.DeleteMessageBatchInput{
   372  			QueueUrl: aws.String(a.conf.SQSURL),
   373  			Entries:  deleteHandles,
   374  		}
   375  
   376  		// trim input entries to max size
   377  		if len(deleteHandles) > 10 {
   378  			input.Entries, deleteHandles = deleteHandles[:10], deleteHandles[10:]
   379  		} else {
   380  			deleteHandles = nil
   381  		}
   382  
   383  		if res, serr := a.sqs.DeleteMessageBatchWithContext(ctx, &input); serr != nil {
   384  			a.log.Errorf("Failed to delete consumed SQS messages: %v\n", serr)
   385  		} else {
   386  			for _, fail := range res.Failed {
   387  				a.log.Errorf("Failed to delete consumed SQS message '%v', response code: %v\n", *fail.Id, *fail.Code)
   388  			}
   389  		}
   390  	}
   391  }
   392  
   393  func (a *AmazonS3) readSQSEvents() error {
   394  	var dudMessageHandles []*sqs.ChangeMessageVisibilityBatchRequestEntry
   395  	addDudFn := func(m *sqs.Message) {
   396  		dudMessageHandles = append(dudMessageHandles, &sqs.ChangeMessageVisibilityBatchRequestEntry{
   397  			Id:                m.MessageId,
   398  			ReceiptHandle:     m.ReceiptHandle,
   399  			VisibilityTimeout: aws.Int64(0),
   400  		})
   401  	}
   402  
   403  	output, err := a.sqs.ReceiveMessage(&sqs.ReceiveMessageInput{
   404  		QueueUrl:            aws.String(a.conf.SQSURL),
   405  		MaxNumberOfMessages: aws.Int64(a.conf.SQSMaxMessages),
   406  		WaitTimeSeconds:     aws.Int64(int64(a.timeout.Seconds())),
   407  	})
   408  	if err != nil {
   409  		return err
   410  	}
   411  
   412  	for _, sqsMsg := range output.Messages {
   413  		msgHandle := &sqs.DeleteMessageBatchRequestEntry{
   414  			Id:            sqsMsg.MessageId,
   415  			ReceiptHandle: sqsMsg.ReceiptHandle,
   416  		}
   417  
   418  		if sqsMsg.Body == nil {
   419  			addDudFn(sqsMsg)
   420  			a.log.Errorln("Received empty SQS message")
   421  			continue
   422  		}
   423  
   424  		items, err := a.parseItemPaths(sqsMsg.Body)
   425  		if err != nil {
   426  			addDudFn(sqsMsg)
   427  			a.log.Errorf("SQS error: %v\n", err)
   428  			continue
   429  		}
   430  
   431  		for _, item := range items {
   432  			a.targetKeys = append(a.targetKeys, objKey{
   433  				s3Key:    item.key,
   434  				s3Bucket: item.bucket,
   435  				attempts: a.conf.Retries,
   436  			})
   437  		}
   438  		a.targetKeys[len(a.targetKeys)-1].sqsHandle = msgHandle
   439  	}
   440  
   441  	// Discard any SQS messages not associated with a target file.
   442  	for len(dudMessageHandles) > 0 {
   443  		input := sqs.ChangeMessageVisibilityBatchInput{
   444  			QueueUrl: aws.String(a.conf.SQSURL),
   445  			Entries:  dudMessageHandles,
   446  		}
   447  
   448  		// trim input entries to max size
   449  		if len(dudMessageHandles) > 10 {
   450  			input.Entries, dudMessageHandles = dudMessageHandles[:10], dudMessageHandles[10:]
   451  		} else {
   452  			dudMessageHandles = nil
   453  		}
   454  		a.sqs.ChangeMessageVisibilityBatch(&input)
   455  	}
   456  
   457  	if len(a.targetKeys) == 0 {
   458  		return types.ErrTimeout
   459  	}
   460  	return nil
   461  }
   462  
   463  func (a *AmazonS3) pushReadKey(key objKey) {
   464  	a.readKeys = append(a.readKeys, key)
   465  }
   466  
   467  func (a *AmazonS3) popTargetKey() {
   468  	if len(a.targetKeys) == 0 {
   469  		return
   470  	}
   471  	if len(a.targetKeys) > 1 {
   472  		a.targetKeys = a.targetKeys[1:]
   473  	} else {
   474  		a.targetKeys = nil
   475  	}
   476  }
   477  
   478  // ReadWithContext attempts to read a new message from the target S3 bucket.
   479  func (a *AmazonS3) ReadWithContext(ctx context.Context) (types.Message, AsyncAckFn, error) {
   480  	a.targetKeysMut.Lock()
   481  	defer a.targetKeysMut.Unlock()
   482  
   483  	if a.session == nil {
   484  		return nil, nil, types.ErrNotConnected
   485  	}
   486  
   487  	if len(a.targetKeys) == 0 {
   488  		if a.sqs != nil {
   489  			if err := a.readSQSEvents(); err != nil {
   490  				return nil, nil, err
   491  			}
   492  		} else {
   493  			// If we aren't using SQS but exhausted our targets we are done.
   494  			return nil, nil, types.ErrTypeClosed
   495  		}
   496  	}
   497  	if len(a.targetKeys) == 0 {
   498  		return nil, nil, types.ErrTimeout
   499  	}
   500  
   501  	msg := message.New(nil)
   502  
   503  	part, obj, err := a.readMethod()
   504  	if err != nil {
   505  		return nil, nil, err
   506  	}
   507  
   508  	msg.Append(part)
   509  	return msg, func(rctx context.Context, res types.Response) error {
   510  		if res.Error() == nil {
   511  			a.deleteObjects([]objKey{obj})
   512  		} else {
   513  			if a.conf.SQSURL == "" {
   514  				a.targetKeysMut.Lock()
   515  				// nolint:gocritic // Ignore appendAssign: append result not assigned to the same slice
   516  				a.targetKeys = append(a.readKeys, obj)
   517  				a.targetKeysMut.Unlock()
   518  			} else {
   519  				a.rejectObjects([]objKey{obj})
   520  			}
   521  		}
   522  		return nil
   523  	}, nil
   524  }
   525  
   526  // Read attempts to read a new message from the target S3 bucket.
   527  func (a *AmazonS3) Read() (types.Message, error) {
   528  	a.targetKeysMut.Lock()
   529  	defer a.targetKeysMut.Unlock()
   530  
   531  	if a.session == nil {
   532  		return nil, types.ErrNotConnected
   533  	}
   534  
   535  	timeoutAt := time.Now().Add(a.timeout)
   536  
   537  	if len(a.targetKeys) == 0 {
   538  		if a.sqs != nil {
   539  			if err := a.readSQSEvents(); err != nil {
   540  				return nil, err
   541  			}
   542  		} else {
   543  			// If we aren't using SQS but exhausted our targets we are done.
   544  			return nil, types.ErrTypeClosed
   545  		}
   546  	}
   547  	if len(a.targetKeys) == 0 {
   548  		return nil, types.ErrTimeout
   549  	}
   550  
   551  	msg := message.New(nil)
   552  
   553  	for len(a.targetKeys) > 0 && msg.Len() < a.conf.MaxBatchCount && time.Until(timeoutAt) > 0 {
   554  		part, objKey, err := a.readMethod()
   555  		if err != nil {
   556  			if err == types.ErrTimeout {
   557  				break
   558  			}
   559  			a.log.Errorf("Error: %v\n", err)
   560  			if msg.Len() == 0 {
   561  				return nil, err
   562  			}
   563  		} else {
   564  			msg.Append(part)
   565  			a.pushReadKey(objKey)
   566  		}
   567  	}
   568  	if msg.Len() == 0 {
   569  		return nil, types.ErrTimeout
   570  	}
   571  
   572  	return msg, nil
   573  }
   574  
   575  func addS3Metadata(p types.Part, obj *s3.GetObjectOutput) {
   576  	meta := p.Metadata()
   577  	if obj.LastModified != nil {
   578  		meta.Set("s3_last_modified", obj.LastModified.Format(time.RFC3339))
   579  		meta.Set("s3_last_modified_unix", strconv.FormatInt(obj.LastModified.Unix(), 10))
   580  	}
   581  	if obj.ContentType != nil {
   582  		meta.Set("s3_content_type", *obj.ContentType)
   583  	}
   584  	if obj.ContentEncoding != nil {
   585  		meta.Set("s3_content_encoding", *obj.ContentEncoding)
   586  	}
   587  }
   588  
   589  // read attempts to read a new message from the target S3 bucket.
   590  func (a *AmazonS3) read() (types.Part, objKey, error) {
   591  	target := a.targetKeys[0]
   592  
   593  	bucket := a.conf.Bucket
   594  	if len(target.s3Bucket) > 0 {
   595  		bucket = target.s3Bucket
   596  	}
   597  	obj, err := a.s3.GetObject(&s3.GetObjectInput{
   598  		Bucket: aws.String(bucket),
   599  		Key:    aws.String(target.s3Key),
   600  	})
   601  	if err != nil {
   602  		target.attempts--
   603  		if target.attempts == 0 {
   604  			// Remove the target file from our list.
   605  			a.popTargetKey()
   606  			a.log.Errorf("Failed to download file '%s' from bucket '%s' after '%v' attempts: %v\n", target.s3Key, bucket, a.conf.Retries, err)
   607  		} else {
   608  			a.targetKeys[0] = target
   609  			return nil, objKey{}, fmt.Errorf("failed to download file '%s' from bucket '%s': %v", target.s3Key, bucket, err)
   610  		}
   611  		return nil, objKey{}, types.ErrTimeout
   612  	}
   613  
   614  	bytes, err := io.ReadAll(obj.Body)
   615  	obj.Body.Close()
   616  	if err != nil {
   617  		a.popTargetKey()
   618  		return nil, objKey{}, fmt.Errorf("failed to download file '%s' from bucket '%s': %v", target.s3Key, bucket, err)
   619  	}
   620  
   621  	part := message.NewPart(bytes)
   622  	meta := part.Metadata()
   623  	for k, v := range obj.Metadata {
   624  		meta.Set(k, *v)
   625  	}
   626  	meta.Set("s3_key", target.s3Key)
   627  	meta.Set("s3_bucket", bucket)
   628  	addS3Metadata(part, obj)
   629  
   630  	a.popTargetKey()
   631  	return part, target, nil
   632  }
   633  
   634  // readFromMgr attempts to read a new message from the target S3 bucket using a
   635  // download manager.
   636  func (a *AmazonS3) readFromMgr() (types.Part, objKey, error) {
   637  	target := a.targetKeys[0]
   638  
   639  	buff := &aws.WriteAtBuffer{}
   640  
   641  	bucket := a.conf.Bucket
   642  	if len(target.s3Bucket) > 0 {
   643  		bucket = target.s3Bucket
   644  	}
   645  
   646  	// Write the contents of S3 Object to the file
   647  	if _, err := a.downloader.Download(buff, &s3.GetObjectInput{
   648  		Bucket: aws.String(bucket),
   649  		Key:    aws.String(target.s3Key),
   650  	}); err != nil {
   651  		target.attempts--
   652  		if target.attempts == 0 {
   653  			// Remove the target file from our list.
   654  			a.popTargetKey()
   655  			a.log.Errorf("Failed to download file '%s' from bucket '%s' after '%v' attempts: %v\n", target.s3Key, bucket, a.conf.Retries, err)
   656  		} else {
   657  			a.targetKeys[0] = target
   658  			return nil, objKey{}, fmt.Errorf("failed to download file '%s' from bucket '%s': %v", target.s3Key, bucket, err)
   659  		}
   660  		return nil, objKey{}, types.ErrTimeout
   661  	}
   662  
   663  	part := message.NewPart(buff.Bytes())
   664  	part.Metadata().
   665  		Set("s3_key", target.s3Key).
   666  		Set("s3_bucket", bucket)
   667  
   668  	a.popTargetKey()
   669  	return part, target, nil
   670  }
   671  
   672  // Acknowledge confirms whether or not our unacknowledged messages have been
   673  // successfully propagated or not.
   674  func (a *AmazonS3) Acknowledge(err error) error {
   675  	if err == nil {
   676  		a.deleteObjects(a.readKeys)
   677  	} else {
   678  		if a.sqs == nil {
   679  			a.targetKeysMut.Lock()
   680  			a.targetKeys = append(a.readKeys, a.targetKeys...)
   681  			a.targetKeysMut.Unlock()
   682  		} else {
   683  			a.rejectObjects(a.readKeys)
   684  		}
   685  	}
   686  	a.readKeys = nil
   687  	return nil
   688  }
   689  
   690  // CloseAsync begins cleaning up resources used by this reader asynchronously.
   691  func (a *AmazonS3) CloseAsync() {
   692  	go func() {
   693  		a.targetKeysMut.Lock()
   694  		a.rejectObjects(a.targetKeys)
   695  		a.targetKeys = nil
   696  		a.targetKeysMut.Unlock()
   697  	}()
   698  }
   699  
   700  // WaitForClose will block until either the reader is closed or a specified
   701  // timeout occurs.
   702  func (a *AmazonS3) WaitForClose(time.Duration) error {
   703  	return nil
   704  }
   705  
   706  //------------------------------------------------------------------------------