github.com/argoproj/argo-events@v1.9.1/eventsources/sources/azurequeuestorage/start.go (about)

     1  package azurequeuestorage
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
    11  	"github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue"
    12  	"go.uber.org/zap"
    13  
    14  	"github.com/argoproj/argo-events/common"
    15  	"github.com/argoproj/argo-events/common/logging"
    16  	eventsourcecommon "github.com/argoproj/argo-events/eventsources/common"
    17  	"github.com/argoproj/argo-events/eventsources/sources"
    18  	metrics "github.com/argoproj/argo-events/metrics"
    19  	apicommon "github.com/argoproj/argo-events/pkg/apis/common"
    20  	"github.com/argoproj/argo-events/pkg/apis/events"
    21  	"github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    22  )
    23  
    24  // EventListener implements Eventing for azure events hub event source
    25  type EventListener struct {
    26  	EventSourceName              string
    27  	EventName                    string
    28  	AzureQueueStorageEventSource v1alpha1.AzureQueueStorageEventSource
    29  	Metrics                      *metrics.Metrics
    30  }
    31  
    32  // GetEventSourceName returns name of event source
    33  func (el *EventListener) GetEventSourceName() string {
    34  	return el.EventSourceName
    35  }
    36  
    37  // GetEventName returns name of event
    38  func (el *EventListener) GetEventName() string {
    39  	return el.EventName
    40  }
    41  
    42  // GetEventSourceType return type of event server
    43  func (el *EventListener) GetEventSourceType() apicommon.EventSourceType {
    44  	return apicommon.AzureQueueStorage
    45  }
    46  
    47  // StartListening starts listening events
    48  func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byte, ...eventsourcecommon.Option) error) error {
    49  	log := logging.FromContext(ctx).
    50  		With(logging.LabelEventSourceType, el.GetEventSourceType(), logging.LabelEventName, el.GetEventName())
    51  
    52  	log.Info("started processing the Azure Queue Storage event source...")
    53  	defer sources.Recover(el.GetEventName())
    54  
    55  	queueStorageEventSource := &el.AzureQueueStorageEventSource
    56  	var client *azqueue.ServiceClient
    57  	// if connectionString is set then use it
    58  	// otherwise try to connect via Azure Active Directory (AAD) with storageAccountName
    59  	if queueStorageEventSource.ConnectionString != nil {
    60  		connStr, err := common.GetSecretFromVolume(queueStorageEventSource.ConnectionString)
    61  		if err != nil {
    62  			log.With("connection-string", queueStorageEventSource.ConnectionString.Name).Errorw("failed to retrieve connection string from secret", zap.Error(err))
    63  			return err
    64  		}
    65  
    66  		log.Info("connecting to azure queue storage with connection string...")
    67  		client, err = azqueue.NewServiceClientFromConnectionString(connStr, nil)
    68  		if err != nil {
    69  			log.Errorw("failed to create a service client", zap.Error(err))
    70  			return err
    71  		}
    72  	} else {
    73  		cred, err := azidentity.NewDefaultAzureCredential(nil)
    74  		if err != nil {
    75  			log.Errorw("failed to create DefaultAzureCredential", zap.Error(err))
    76  			return err
    77  		}
    78  		log.Info("connecting to azure queue storage with AAD credentials...")
    79  		serviceURL := fmt.Sprintf("https://%s.queue.core.windows.net/", queueStorageEventSource.StorageAccountName)
    80  		client, err = azqueue.NewServiceClient(serviceURL, cred, nil)
    81  		if err != nil {
    82  			log.Errorw("failed to create a service client", zap.Error(err))
    83  			return err
    84  		}
    85  	}
    86  
    87  	queueClient := client.NewQueueClient(el.AzureQueueStorageEventSource.QueueName)
    88  	if queueStorageEventSource.JSONBody {
    89  		log.Info("assuming all events have a json body...")
    90  	}
    91  	var numMessages int32 = 10
    92  	var visibilityTimeout int32 = 120
    93  	var waitTime int32 = 3 // Defaults to 3 seconds
    94  	if el.AzureQueueStorageEventSource.WaitTimeInSeconds != nil {
    95  		waitTime = *el.AzureQueueStorageEventSource.WaitTimeInSeconds
    96  	}
    97  	log.Info("listening for messages on the queue...")
    98  	for {
    99  		select {
   100  		case <-ctx.Done():
   101  			log.Info("exiting AQS event listener...")
   102  			return nil
   103  		default:
   104  		}
   105  		log.Info("dequeing messages....")
   106  		messages, err := queueClient.DequeueMessages(ctx, &azqueue.DequeueMessagesOptions{
   107  			NumberOfMessages:  &numMessages,
   108  			VisibilityTimeout: &visibilityTimeout,
   109  		})
   110  		if err != nil {
   111  			log.Errorw("failed to get messages from AQS", zap.Error(err))
   112  			time.Sleep(time.Second)
   113  			continue
   114  		}
   115  		for _, m := range messages.Messages {
   116  			el.processMessage(m, dispatch, func() {
   117  				_, err = queueClient.DeleteMessage(ctx, *m.MessageID, *m.PopReceipt, &azqueue.DeleteMessageOptions{})
   118  				if err != nil {
   119  					log.Errorw("Failed to delete message", zap.Error(err))
   120  				}
   121  			}, log)
   122  		}
   123  		if len(messages.Messages) == 0 {
   124  			time.Sleep(time.Second * time.Duration(waitTime))
   125  		}
   126  	}
   127  }
   128  
   129  func (el *EventListener) processMessage(message *azqueue.DequeuedMessage, dispatch func([]byte, ...eventsourcecommon.Option) error, ack func(), log *zap.SugaredLogger) {
   130  	defer func(start time.Time) {
   131  		el.Metrics.EventProcessingDuration(el.GetEventSourceName(), el.GetEventName(), float64(time.Since(start)/time.Millisecond))
   132  	}(time.Now())
   133  	data := &events.AzureQueueStorageEventData{
   134  		MessageID:     *message.MessageID,
   135  		InsertionTime: *message.InsertionTime,
   136  		Metadata:      el.AzureQueueStorageEventSource.Metadata,
   137  	}
   138  	body := []byte(*message.MessageText)
   139  	if el.AzureQueueStorageEventSource.DecodeMessage {
   140  		rawDecodedText, err := base64.RawURLEncoding.DecodeString(*message.MessageText)
   141  		if err != nil {
   142  			log.Errorw("failed to base64 decode message...", zap.Error(err))
   143  			el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName())
   144  			if !el.AzureQueueStorageEventSource.DLQ {
   145  				ack()
   146  			}
   147  			return
   148  		}
   149  		body = rawDecodedText
   150  	}
   151  	if el.AzureQueueStorageEventSource.JSONBody {
   152  		data.Body = (*json.RawMessage)(&body)
   153  	} else {
   154  		data.Body = body
   155  	}
   156  	eventBytes, err := json.Marshal(data)
   157  	if err != nil {
   158  		log.Errorw("failed to marshal event data, will process next message...", zap.Error(err))
   159  		el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName())
   160  		// Don't ack if a DLQ is configured to allow to forward the message to the DLQ
   161  		if !el.AzureQueueStorageEventSource.DLQ {
   162  			ack()
   163  		}
   164  		return
   165  	}
   166  	if err = dispatch(eventBytes); err != nil {
   167  		log.Errorw("failed to dispatch azure queue storage event", zap.Error(err))
   168  		el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName())
   169  	} else {
   170  		ack()
   171  	}
   172  }