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

     1  package reader
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/Jeffail/benthos/v3/lib/log"
     9  	"github.com/Jeffail/benthos/v3/lib/message"
    10  	"github.com/Jeffail/benthos/v3/lib/metrics"
    11  	"github.com/Jeffail/benthos/v3/lib/types"
    12  	sess "github.com/Jeffail/benthos/v3/lib/util/aws/session"
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/aws/request"
    16  	"github.com/aws/aws-sdk-go/aws/session"
    17  	"github.com/aws/aws-sdk-go/service/sqs"
    18  )
    19  
    20  //------------------------------------------------------------------------------
    21  
    22  // AmazonSQSConfig contains configuration values for the input type.
    23  type AmazonSQSConfig struct {
    24  	sess.Config         `json:",inline" yaml:",inline"`
    25  	URL                 string `json:"url" yaml:"url"`
    26  	Timeout             string `json:"timeout" yaml:"timeout"`
    27  	MaxNumberOfMessages int64  `json:"max_number_of_messages" yaml:"max_number_of_messages"`
    28  	DeleteMessage       bool   `json:"delete_message" yaml:"delete_message"`
    29  }
    30  
    31  // NewAmazonSQSConfig creates a new Config with default values.
    32  func NewAmazonSQSConfig() AmazonSQSConfig {
    33  	return AmazonSQSConfig{
    34  		Config:              sess.NewConfig(),
    35  		URL:                 "",
    36  		Timeout:             "5s",
    37  		MaxNumberOfMessages: 1,
    38  		DeleteMessage:       true,
    39  	}
    40  }
    41  
    42  //------------------------------------------------------------------------------
    43  
    44  // AmazonSQS is a benthos reader.Type implementation that reads messages from an
    45  // Amazon SQS queue.
    46  type AmazonSQS struct {
    47  	conf AmazonSQSConfig
    48  
    49  	pendingHandles map[string]string
    50  
    51  	session *session.Session
    52  	sqs     *sqs.SQS
    53  	timeout time.Duration
    54  
    55  	log   log.Modular
    56  	stats metrics.Type
    57  }
    58  
    59  // NewAmazonSQS creates a new Amazon SQS reader.Type.
    60  func NewAmazonSQS(
    61  	conf AmazonSQSConfig,
    62  	log log.Modular,
    63  	stats metrics.Type,
    64  ) (*AmazonSQS, error) {
    65  	var timeout time.Duration
    66  	if tout := conf.Timeout; len(tout) > 0 {
    67  		var err error
    68  		if timeout, err = time.ParseDuration(tout); err != nil {
    69  			return nil, fmt.Errorf("failed to parse timeout string: %v", err)
    70  		}
    71  	}
    72  	return &AmazonSQS{
    73  		conf:           conf,
    74  		log:            log,
    75  		stats:          stats,
    76  		timeout:        timeout,
    77  		pendingHandles: map[string]string{},
    78  	}, nil
    79  }
    80  
    81  // Connect attempts to establish a connection to the target SQS queue.
    82  func (a *AmazonSQS) Connect() error {
    83  	return a.ConnectWithContext(context.Background())
    84  }
    85  
    86  // ConnectWithContext attempts to establish a connection to the target SQS
    87  // queue.
    88  func (a *AmazonSQS) ConnectWithContext(ctx context.Context) error {
    89  	if a.session != nil {
    90  		return nil
    91  	}
    92  
    93  	sess, err := a.conf.GetSession()
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	a.sqs = sqs.New(sess)
    99  	a.session = sess
   100  
   101  	a.log.Infof("Receiving Amazon SQS messages from URL: %v\n", a.conf.URL)
   102  	return nil
   103  }
   104  
   105  func addSQSMetadata(p types.Part, sqsMsg *sqs.Message) {
   106  	meta := p.Metadata()
   107  	meta.Set("sqs_message_id", *sqsMsg.MessageId)
   108  	meta.Set("sqs_receipt_handle", *sqsMsg.ReceiptHandle)
   109  	if rCountStr := sqsMsg.Attributes["ApproximateReceiveCount"]; rCountStr != nil {
   110  		meta.Set("sqs_approximate_receive_count", *rCountStr)
   111  	}
   112  	for k, v := range sqsMsg.MessageAttributes {
   113  		if v.StringValue != nil {
   114  			meta.Set(k, *v.StringValue)
   115  		}
   116  	}
   117  }
   118  
   119  // ReadWithContext attempts to read a new message from the target SQS.
   120  func (a *AmazonSQS) ReadWithContext(ctx context.Context) (types.Message, AsyncAckFn, error) {
   121  	if a.session == nil {
   122  		return nil, nil, types.ErrNotConnected
   123  	}
   124  
   125  	msg := message.New(nil)
   126  	pendingHandles := map[string]string{}
   127  
   128  	output, err := a.sqs.ReceiveMessageWithContext(ctx, &sqs.ReceiveMessageInput{
   129  		QueueUrl:              aws.String(a.conf.URL),
   130  		MaxNumberOfMessages:   aws.Int64(a.conf.MaxNumberOfMessages),
   131  		WaitTimeSeconds:       aws.Int64(int64(a.timeout.Seconds())),
   132  		AttributeNames:        []*string{aws.String("All")},
   133  		MessageAttributeNames: []*string{aws.String("All")},
   134  	})
   135  	if err != nil {
   136  		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
   137  			return nil, nil, types.ErrTimeout
   138  		}
   139  		return nil, nil, err
   140  	}
   141  	for _, sqsMsg := range output.Messages {
   142  		if sqsMsg.ReceiptHandle != nil {
   143  			pendingHandles[*sqsMsg.MessageId] = *sqsMsg.ReceiptHandle
   144  		}
   145  
   146  		if sqsMsg.Body != nil {
   147  			part := message.NewPart([]byte(*sqsMsg.Body))
   148  			addSQSMetadata(part, sqsMsg)
   149  			msg.Append(part)
   150  		}
   151  	}
   152  	if msg.Len() == 0 {
   153  		return nil, nil, types.ErrTimeout
   154  	}
   155  
   156  	return msg, func(rctx context.Context, res types.Response) error {
   157  		// TODO: Replace this with a background process for batching these
   158  		// requests up more.
   159  		if res.Error() == nil {
   160  			if !a.conf.DeleteMessage {
   161  				return nil
   162  			}
   163  			for len(pendingHandles) > 0 {
   164  				input := sqs.DeleteMessageBatchInput{
   165  					QueueUrl: aws.String(a.conf.URL),
   166  				}
   167  
   168  				for k, v := range pendingHandles {
   169  					input.Entries = append(input.Entries, &sqs.DeleteMessageBatchRequestEntry{
   170  						Id:            aws.String(k),
   171  						ReceiptHandle: aws.String(v),
   172  					})
   173  					delete(pendingHandles, k)
   174  					if len(input.Entries) == 10 {
   175  						break
   176  					}
   177  				}
   178  
   179  				response, serr := a.sqs.DeleteMessageBatchWithContext(rctx, &input)
   180  				if serr != nil {
   181  					a.log.Errorf("Failed to delete consumed SQS messages: %v\n", serr)
   182  					return serr
   183  				}
   184  				for _, fail := range response.Failed {
   185  					a.log.Errorf("Failed to delete consumed SQS message '%v', response code: %v\n", *fail.Id, *fail.Code)
   186  				}
   187  			}
   188  		} else {
   189  			for len(pendingHandles) > 0 {
   190  				input := sqs.ChangeMessageVisibilityBatchInput{
   191  					QueueUrl: aws.String(a.conf.URL),
   192  				}
   193  
   194  				for k, v := range pendingHandles {
   195  					input.Entries = append(input.Entries, &sqs.ChangeMessageVisibilityBatchRequestEntry{
   196  						Id:                aws.String(k),
   197  						ReceiptHandle:     aws.String(v),
   198  						VisibilityTimeout: aws.Int64(0),
   199  					})
   200  					delete(pendingHandles, k)
   201  					if len(input.Entries) == 10 {
   202  						break
   203  					}
   204  				}
   205  
   206  				response, serr := a.sqs.ChangeMessageVisibilityBatchWithContext(rctx, &input)
   207  				if serr != nil {
   208  					a.log.Errorf("Failed to change consumed SQS message visibility: %v\n", serr)
   209  					return serr
   210  				}
   211  				for _, fail := range response.Failed {
   212  					a.log.Errorf("Failed to change consumed SQS message '%v' visibility, response code: %v\n", *fail.Id, *fail.Code)
   213  				}
   214  			}
   215  		}
   216  		return nil
   217  	}, nil
   218  }
   219  
   220  // Read attempts to read a new message from the target SQS.
   221  func (a *AmazonSQS) Read() (types.Message, error) {
   222  	if a.session == nil {
   223  		return nil, types.ErrNotConnected
   224  	}
   225  
   226  	output, err := a.sqs.ReceiveMessage(&sqs.ReceiveMessageInput{
   227  		QueueUrl:              aws.String(a.conf.URL),
   228  		MaxNumberOfMessages:   aws.Int64(a.conf.MaxNumberOfMessages),
   229  		WaitTimeSeconds:       aws.Int64(int64(a.timeout.Seconds())),
   230  		AttributeNames:        []*string{aws.String("All")},
   231  		MessageAttributeNames: []*string{aws.String("All")},
   232  	})
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	msg := message.New(nil)
   238  
   239  	if len(output.Messages) == 0 {
   240  		return nil, types.ErrTimeout
   241  	}
   242  
   243  	for _, sqsMsg := range output.Messages {
   244  		if sqsMsg.ReceiptHandle != nil {
   245  			a.pendingHandles[*sqsMsg.MessageId] = *sqsMsg.ReceiptHandle
   246  		}
   247  
   248  		if sqsMsg.Body != nil {
   249  			part := message.NewPart([]byte(*sqsMsg.Body))
   250  			addSQSMetadata(part, sqsMsg)
   251  			msg.Append(part)
   252  		}
   253  	}
   254  
   255  	if msg.Len() == 0 {
   256  		return nil, types.ErrTimeout
   257  	}
   258  
   259  	return msg, nil
   260  }
   261  
   262  // Acknowledge confirms whether or not our unacknowledged messages have been
   263  // successfully propagated or not.
   264  func (a *AmazonSQS) Acknowledge(err error) error {
   265  	if err == nil {
   266  		for len(a.pendingHandles) > 0 {
   267  			if !a.conf.DeleteMessage {
   268  				// If we aren't deleting the source here then simply remove the
   269  				// handles.
   270  				for k := range a.pendingHandles {
   271  					delete(a.pendingHandles, k)
   272  				}
   273  				return nil
   274  			}
   275  
   276  			input := sqs.DeleteMessageBatchInput{
   277  				QueueUrl: aws.String(a.conf.URL),
   278  			}
   279  
   280  			for k, v := range a.pendingHandles {
   281  				input.Entries = append(input.Entries, &sqs.DeleteMessageBatchRequestEntry{
   282  					Id:            aws.String(k),
   283  					ReceiptHandle: aws.String(v),
   284  				})
   285  				delete(a.pendingHandles, k)
   286  				if len(input.Entries) == 10 {
   287  					break
   288  				}
   289  			}
   290  
   291  			if res, serr := a.sqs.DeleteMessageBatch(&input); serr != nil {
   292  				a.log.Errorf("Failed to delete consumed SQS messages: %v\n", serr)
   293  			} else {
   294  				for _, fail := range res.Failed {
   295  					a.log.Errorf("Failed to delete consumed SQS message '%v', response code: %v\n", *fail.Id, *fail.Code)
   296  				}
   297  			}
   298  		}
   299  	} else {
   300  		for len(a.pendingHandles) > 0 {
   301  			input := sqs.ChangeMessageVisibilityBatchInput{
   302  				QueueUrl: aws.String(a.conf.URL),
   303  			}
   304  
   305  			for k, v := range a.pendingHandles {
   306  				input.Entries = append(input.Entries, &sqs.ChangeMessageVisibilityBatchRequestEntry{
   307  					Id:                aws.String(k),
   308  					ReceiptHandle:     aws.String(v),
   309  					VisibilityTimeout: aws.Int64(0),
   310  				})
   311  				delete(a.pendingHandles, k)
   312  				if len(input.Entries) == 10 {
   313  					break
   314  				}
   315  			}
   316  
   317  			if res, serr := a.sqs.ChangeMessageVisibilityBatch(&input); serr != nil {
   318  				a.log.Errorf("Failed to change consumed SQS message visibility: %v\n", serr)
   319  			} else {
   320  				for _, fail := range res.Failed {
   321  					a.log.Errorf("Failed to change consumed SQS message '%v' visibility, response code: %v\n", *fail.Id, *fail.Code)
   322  				}
   323  			}
   324  		}
   325  	}
   326  	return nil
   327  }
   328  
   329  // CloseAsync begins cleaning up resources used by this reader asynchronously.
   330  func (a *AmazonSQS) CloseAsync() {
   331  }
   332  
   333  // WaitForClose will block until either the reader is closed or a specified
   334  // timeout occurs.
   335  func (a *AmazonSQS) WaitForClose(time.Duration) error {
   336  	return nil
   337  }
   338  
   339  //------------------------------------------------------------------------------