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

     1  /*
     2  Copyright 2018 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 resource
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  	"regexp"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/tidwall/gjson"
    29  	"go.uber.org/zap"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"k8s.io/apimachinery/pkg/labels"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	"k8s.io/apimachinery/pkg/selection"
    35  	"k8s.io/client-go/dynamic"
    36  	"k8s.io/client-go/dynamic/dynamicinformer"
    37  	"k8s.io/client-go/tools/cache"
    38  
    39  	"github.com/argoproj/argo-events/common"
    40  	"github.com/argoproj/argo-events/common/logging"
    41  	eventsourcecommon "github.com/argoproj/argo-events/eventsources/common"
    42  	"github.com/argoproj/argo-events/eventsources/sources"
    43  	metrics "github.com/argoproj/argo-events/metrics"
    44  	apicommon "github.com/argoproj/argo-events/pkg/apis/common"
    45  	"github.com/argoproj/argo-events/pkg/apis/events"
    46  	"github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    47  )
    48  
    49  // InformerEvent holds event generated from resource state change
    50  type InformerEvent struct {
    51  	Obj    interface{}
    52  	OldObj interface{}
    53  	Type   v1alpha1.ResourceEventType
    54  }
    55  
    56  // EventListener implements Eventing
    57  type EventListener struct {
    58  	EventSourceName     string
    59  	EventName           string
    60  	ResourceEventSource v1alpha1.ResourceEventSource
    61  	Metrics             *metrics.Metrics
    62  }
    63  
    64  // GetEventSourceName returns name of event source
    65  func (el *EventListener) GetEventSourceName() string {
    66  	return el.EventSourceName
    67  }
    68  
    69  // GetEventName returns name of event
    70  func (el *EventListener) GetEventName() string {
    71  	return el.EventName
    72  }
    73  
    74  // GetEventSourceType return type of event server
    75  func (el *EventListener) GetEventSourceType() apicommon.EventSourceType {
    76  	return apicommon.ResourceEvent
    77  }
    78  
    79  // StartListening watches resource updates and consume those events
    80  func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byte, ...eventsourcecommon.Option) error) error {
    81  	log := logging.FromContext(ctx).
    82  		With(logging.LabelEventSourceType, el.GetEventSourceType(), logging.LabelEventName, el.GetEventName())
    83  	defer sources.Recover(el.GetEventName())
    84  
    85  	log.Info("setting up a K8s client")
    86  	kubeConfig, _ := os.LookupEnv(common.EnvVarKubeConfig)
    87  	restConfig, err := common.GetClientConfig(kubeConfig)
    88  	if err != nil {
    89  		return fmt.Errorf("failed to get a K8s rest config for the event source %s, %w", el.GetEventName(), err)
    90  	}
    91  	client, err := dynamic.NewForConfig(restConfig)
    92  	if err != nil {
    93  		return fmt.Errorf("failed to set up a dynamic K8s client for the event source %s, %w", el.GetEventName(), err)
    94  	}
    95  
    96  	resourceEventSource := &el.ResourceEventSource
    97  
    98  	gvr := schema.GroupVersionResource{
    99  		Group:    resourceEventSource.Group,
   100  		Version:  resourceEventSource.Version,
   101  		Resource: resourceEventSource.Resource,
   102  	}
   103  
   104  	client.Resource(gvr)
   105  
   106  	options := &metav1.ListOptions{}
   107  
   108  	log.Info("configuring label selectors if filters are selected...")
   109  	if resourceEventSource.Filter != nil && resourceEventSource.Filter.Labels != nil {
   110  		sel, err := LabelSelector(resourceEventSource.Filter.Labels)
   111  		if err != nil {
   112  			return fmt.Errorf("failed to create the label selector for the event source %s, %w", el.GetEventName(), err)
   113  		}
   114  		options.LabelSelector = sel.String()
   115  	}
   116  
   117  	tweakListOptions := func(op *metav1.ListOptions) {
   118  		*op = *options
   119  	}
   120  
   121  	log.Info("setting up informer factory...")
   122  	factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(client, 0, resourceEventSource.Namespace, tweakListOptions)
   123  
   124  	informer := factory.ForResource(gvr)
   125  
   126  	informerEventCh := make(chan *InformerEvent)
   127  	stopCh := make(chan struct{})
   128  	startTime := time.Now()
   129  
   130  	processOne := func(event *InformerEvent) error {
   131  		if !passFilters(event, resourceEventSource.Filter, startTime, log) {
   132  			return nil
   133  		}
   134  
   135  		defer func(start time.Time) {
   136  			el.Metrics.EventProcessingDuration(el.GetEventSourceName(), el.GetEventName(), float64(time.Since(start)/time.Millisecond))
   137  		}(time.Now())
   138  
   139  		objBody, err := json.Marshal(event.Obj)
   140  		if err != nil {
   141  			return fmt.Errorf("failed to marshal the resource, rejecting the event, %w", err)
   142  		}
   143  
   144  		var oldObjBody []byte
   145  		if event.OldObj != nil {
   146  			oldObjBody, err = json.Marshal(event.OldObj)
   147  			if err != nil {
   148  				return fmt.Errorf("failed to marshal the resource, rejecting the event, %w", err)
   149  			}
   150  		}
   151  
   152  		eventData := &events.ResourceEventData{
   153  			EventType: string(event.Type),
   154  			Body:      (*json.RawMessage)(&objBody),
   155  			OldBody:   (*json.RawMessage)(&oldObjBody),
   156  			Group:     resourceEventSource.Group,
   157  			Version:   resourceEventSource.Version,
   158  			Resource:  resourceEventSource.Resource,
   159  			Metadata:  resourceEventSource.Metadata,
   160  		}
   161  
   162  		eventBody, err := json.Marshal(eventData)
   163  		if err != nil {
   164  			return fmt.Errorf("failed to marshal the event. rejecting the event, %w", err)
   165  		}
   166  
   167  		if err = dispatch(eventBody); err != nil {
   168  			return fmt.Errorf("failed to dispatch a resource event, %w", err)
   169  		}
   170  		return nil
   171  	}
   172  
   173  	go func() {
   174  		log.Info("listening to resource events...")
   175  		for {
   176  			select {
   177  			case event := <-informerEventCh:
   178  				if err := processOne(event); err != nil {
   179  					log.Errorw("failed to process a Resource event", zap.Error(err))
   180  					el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName())
   181  				}
   182  			case <-stopCh:
   183  				return
   184  			}
   185  		}
   186  	}()
   187  
   188  	handlerFuncs := cache.ResourceEventHandlerFuncs{}
   189  
   190  	for _, eventType := range resourceEventSource.EventTypes {
   191  		switch eventType {
   192  		case v1alpha1.ADD:
   193  			handlerFuncs.AddFunc = func(obj interface{}) {
   194  				log.Info("detected create event")
   195  				informerEventCh <- &InformerEvent{
   196  					Obj:  obj,
   197  					Type: v1alpha1.ADD,
   198  				}
   199  			}
   200  		case v1alpha1.UPDATE:
   201  			handlerFuncs.UpdateFunc = func(oldObj, newObj interface{}) {
   202  				log.Info("detected update event")
   203  				uNewObj := newObj.(*unstructured.Unstructured)
   204  				uOldObj := oldObj.(*unstructured.Unstructured)
   205  				if uNewObj.GetResourceVersion() == uOldObj.GetResourceVersion() {
   206  					log.Infof("rejecting update event with identical resource versions: %s", uNewObj.GetResourceVersion())
   207  					return
   208  				}
   209  				informerEventCh <- &InformerEvent{
   210  					Obj:    newObj,
   211  					OldObj: oldObj,
   212  					Type:   v1alpha1.UPDATE,
   213  				}
   214  			}
   215  		case v1alpha1.DELETE:
   216  			handlerFuncs.DeleteFunc = func(obj interface{}) {
   217  				log.Info("detected delete event")
   218  				informerEventCh <- &InformerEvent{
   219  					Obj:  obj,
   220  					Type: v1alpha1.DELETE,
   221  				}
   222  			}
   223  		default:
   224  			stopCh <- struct{}{}
   225  			return fmt.Errorf("unknown event type: %s", string(eventType))
   226  		}
   227  	}
   228  
   229  	sharedInformer := informer.Informer()
   230  	sharedInformer.AddEventHandler(handlerFuncs)
   231  
   232  	doneCh := make(chan struct{})
   233  
   234  	log.Info("running informer...")
   235  	sharedInformer.Run(doneCh)
   236  
   237  	<-ctx.Done()
   238  	doneCh <- struct{}{}
   239  	stopCh <- struct{}{}
   240  
   241  	log.Info("event source is stopped")
   242  	close(informerEventCh)
   243  
   244  	return nil
   245  }
   246  
   247  // LabelReq returns label requirements
   248  func LabelReq(sel v1alpha1.Selector) (*labels.Requirement, error) {
   249  	op := selection.Equals
   250  	if sel.Operation != "" {
   251  		op = selection.Operator(sel.Operation)
   252  	}
   253  	var values []string
   254  	switch {
   255  	case (op == selection.Exists || op == selection.DoesNotExist) && sel.Value == "":
   256  		values = []string{}
   257  	case op == selection.In || op == selection.NotIn:
   258  		values = strings.Split(sel.Value, ",")
   259  	default:
   260  		values = []string{sel.Value}
   261  	}
   262  	req, err := labels.NewRequirement(sel.Key, op, values)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	return req, nil
   267  }
   268  
   269  // LabelSelector returns label selector for resource filtering
   270  func LabelSelector(selectors []v1alpha1.Selector) (labels.Selector, error) {
   271  	var labelRequirements []labels.Requirement
   272  	for _, sel := range selectors {
   273  		req, err := LabelReq(sel)
   274  		if err != nil {
   275  			return nil, err
   276  		}
   277  		labelRequirements = append(labelRequirements, *req)
   278  	}
   279  	return labels.NewSelector().Add(labelRequirements...), nil
   280  }
   281  
   282  // helper method to check if the object passed the user defined filters
   283  func passFilters(event *InformerEvent, filter *v1alpha1.ResourceFilter, startTime time.Time, log *zap.SugaredLogger) bool {
   284  	// no filters are applied.
   285  	if filter == nil {
   286  		return true
   287  	}
   288  	uObj := event.Obj.(*unstructured.Unstructured)
   289  	if len(filter.Prefix) > 0 && !strings.HasPrefix(uObj.GetName(), filter.Prefix) {
   290  		log.Infof("resource name does not match prefix. resource-name: %s, prefix: %s\n", uObj.GetName(), filter.Prefix)
   291  		return false
   292  	}
   293  	eventTime := getEventTime(uObj, event.Type)
   294  	if filter.AfterStart && eventTime.UTC().Before(startTime.UTC()) {
   295  		log.Infof("Event happened before service start time. event-timestamp: %s, start-timestamp: %s\n", eventTime.UTC().String(), startTime.UTC().String())
   296  		return false
   297  	}
   298  	created := uObj.GetCreationTimestamp()
   299  	if !filter.CreatedBy.IsZero() && created.UTC().After(filter.CreatedBy.UTC()) {
   300  		log.Infof("resource is created after filter time. creation-timestamp: %s, filter-creation-timestamp: %s\n", created.UTC().String(), filter.CreatedBy.UTC().String())
   301  		return false
   302  	}
   303  	if len(filter.Fields) > 0 {
   304  		jsData, err := uObj.MarshalJSON()
   305  		if err != nil {
   306  			log.Errorw("failed to marshal informer event", zap.Error(err))
   307  			return false
   308  		}
   309  
   310  		return filterFields(jsData, filter.Fields, log)
   311  	}
   312  	return true
   313  }
   314  
   315  func filterFields(jsonData []byte, selectors []v1alpha1.Selector, log *zap.SugaredLogger) bool {
   316  	for _, selector := range selectors {
   317  		res := gjson.GetBytes(jsonData, selector.Key)
   318  		if !res.Exists() {
   319  			return false
   320  		}
   321  		exp, err := regexp.Compile(selector.Value)
   322  		if err != nil {
   323  			log.Errorw("invalid regex", zap.Error(err))
   324  			return false
   325  		}
   326  		match := exp.Match([]byte(res.Str))
   327  
   328  		switch selection.Operator(selector.Operation) {
   329  		case selection.Equals, selection.DoubleEquals:
   330  			if !match {
   331  				return false
   332  			}
   333  		case selection.NotEquals:
   334  			if match {
   335  				return false
   336  			}
   337  		default:
   338  			log.Errorf("invalid operator, only %v, %v and %v are supported", selection.Equals, selection.DoubleEquals, selection.NotEquals)
   339  			return false
   340  		}
   341  	}
   342  	return true
   343  }
   344  
   345  func getEventTime(obj *unstructured.Unstructured, eventType v1alpha1.ResourceEventType) metav1.Time {
   346  	switch eventType {
   347  	case v1alpha1.ADD:
   348  		return obj.GetCreationTimestamp()
   349  	case v1alpha1.DELETE:
   350  		if obj.GetDeletionTimestamp() != nil {
   351  			return *obj.GetDeletionTimestamp()
   352  		} else {
   353  			return metav1.Now()
   354  		}
   355  	case v1alpha1.UPDATE:
   356  		t := obj.GetCreationTimestamp()
   357  		for _, f := range obj.GetManagedFields() {
   358  			if f.Operation == metav1.ManagedFieldsOperationUpdate && f.Time.UTC().After(t.UTC()) {
   359  				t = *f.Time
   360  			}
   361  		}
   362  		return t
   363  	default:
   364  		return obj.GetCreationTimestamp()
   365  	}
   366  }