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

     1  /*
     2  Copyright 2020 BlackRock, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package redis
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"time"
    24  
    25  	"github.com/go-redis/redis/v8"
    26  	"go.uber.org/zap"
    27  
    28  	"github.com/argoproj/argo-events/common"
    29  	"github.com/argoproj/argo-events/common/logging"
    30  	eventsourcecommon "github.com/argoproj/argo-events/eventsources/common"
    31  	"github.com/argoproj/argo-events/eventsources/sources"
    32  	metrics "github.com/argoproj/argo-events/metrics"
    33  	apicommon "github.com/argoproj/argo-events/pkg/apis/common"
    34  	"github.com/argoproj/argo-events/pkg/apis/events"
    35  	"github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    36  )
    37  
    38  // EventListener implements Eventing for the Redis event source
    39  type EventListener struct {
    40  	EventSourceName  string
    41  	EventName        string
    42  	RedisEventSource v1alpha1.RedisEventSource
    43  	Metrics          *metrics.Metrics
    44  }
    45  
    46  // GetEventSourceName returns name of event source
    47  func (el *EventListener) GetEventSourceName() string {
    48  	return el.EventSourceName
    49  }
    50  
    51  // GetEventName returns name of event
    52  func (el *EventListener) GetEventName() string {
    53  	return el.EventName
    54  }
    55  
    56  // GetEventSourceType return type of event server
    57  func (el *EventListener) GetEventSourceType() apicommon.EventSourceType {
    58  	return apicommon.RedisEvent
    59  }
    60  
    61  // StartListening listens events published by redis
    62  func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byte, ...eventsourcecommon.Option) error) error {
    63  	log := logging.FromContext(ctx).
    64  		With(logging.LabelEventSourceType, el.GetEventSourceType(), logging.LabelEventName, el.GetEventName())
    65  	log.Info("started processing the Redis event source...")
    66  	defer sources.Recover(el.GetEventName())
    67  
    68  	redisEventSource := &el.RedisEventSource
    69  
    70  	opt := &redis.Options{
    71  		Addr: redisEventSource.HostAddress,
    72  		DB:   int(redisEventSource.DB),
    73  	}
    74  
    75  	log.Info("retrieving password if it has been configured...")
    76  	if redisEventSource.Password != nil {
    77  		password, err := common.GetSecretFromVolume(redisEventSource.Password)
    78  		if err != nil {
    79  			return fmt.Errorf("failed to find the secret password %s, %w", redisEventSource.Password.Name, err)
    80  		}
    81  		opt.Password = password
    82  	}
    83  
    84  	if redisEventSource.Username != "" {
    85  		opt.Username = redisEventSource.Username
    86  	}
    87  
    88  	if redisEventSource.TLS != nil {
    89  		tlsConfig, err := common.GetTLSConfig(redisEventSource.TLS)
    90  		if err != nil {
    91  			return fmt.Errorf("failed to get the tls configuration, %w", err)
    92  		}
    93  		opt.TLSConfig = tlsConfig
    94  	}
    95  
    96  	log.Info("setting up a redis client...")
    97  	client := redis.NewClient(opt)
    98  
    99  	if status := client.Ping(ctx); status.Err() != nil {
   100  		return fmt.Errorf("failed to connect to host %s and db %d for event source %s, %w", redisEventSource.HostAddress, redisEventSource.DB, el.GetEventName(), status.Err())
   101  	}
   102  
   103  	pubsub := client.Subscribe(ctx, redisEventSource.Channels...)
   104  	// Wait for confirmation that subscription is created before publishing anything.
   105  	if _, err := pubsub.Receive(ctx); err != nil {
   106  		return fmt.Errorf("failed to receive the subscription confirmation for event source %s, %w", el.GetEventName(), err)
   107  	}
   108  
   109  	// Go channel which receives messages.
   110  	ch := pubsub.Channel()
   111  	for {
   112  		select {
   113  		case message, ok := <-ch:
   114  			if !ok {
   115  				log.Error("failed to read a message, channel might have been closed")
   116  				return fmt.Errorf("channel might have been closed")
   117  			}
   118  
   119  			if err := el.handleOne(message, dispatch, log); err != nil {
   120  				log.With("channel", message.Channel).Errorw("failed to process a Redis message", zap.Error(err))
   121  				el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName())
   122  			}
   123  		case <-ctx.Done():
   124  			log.Info("event source is stopped. unsubscribing the subscription")
   125  			if err := pubsub.Unsubscribe(ctx, redisEventSource.Channels...); err != nil {
   126  				log.Errorw("failed to unsubscribe", zap.Error(err))
   127  			}
   128  			return nil
   129  		}
   130  	}
   131  }
   132  
   133  func (el *EventListener) handleOne(message *redis.Message, dispatch func([]byte, ...eventsourcecommon.Option) error, log *zap.SugaredLogger) error {
   134  	defer func(start time.Time) {
   135  		el.Metrics.EventProcessingDuration(el.GetEventSourceName(), el.GetEventName(), float64(time.Since(start)/time.Millisecond))
   136  	}(time.Now())
   137  
   138  	log.With("channel", message.Channel).Info("received a message")
   139  	eventData := &events.RedisEventData{
   140  		Channel:  message.Channel,
   141  		Pattern:  message.Pattern,
   142  		Body:     message.Payload,
   143  		Metadata: el.RedisEventSource.Metadata,
   144  	}
   145  	if el.RedisEventSource.JSONBody {
   146  		body := []byte(message.Payload)
   147  		eventData.Body = (*json.RawMessage)(&body)
   148  	}
   149  
   150  	eventBody, err := json.Marshal(&eventData)
   151  	if err != nil {
   152  		return fmt.Errorf("failed to marshal the event data, rejecting the event, %w", err)
   153  	}
   154  	log.With("channel", message.Channel).Info("dispatching the event on the data channel...")
   155  	if err = dispatch(eventBody); err != nil {
   156  		return fmt.Errorf("failed dispatch a Redis event, %w", err)
   157  	}
   158  	return nil
   159  }