github.com/argoproj/argo-events@v1.9.1/sensors/dependencies/filter.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 dependencies
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  	"text/template"
    28  	"time"
    29  
    30  	"github.com/Knetic/govaluate"
    31  	"github.com/Masterminds/sprig/v3"
    32  	"github.com/argoproj/argo-events/common"
    33  	"github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1"
    34  	"github.com/tidwall/gjson"
    35  	lua "github.com/yuin/gopher-lua"
    36  )
    37  
    38  const (
    39  	errMsgListSeparator = " / "
    40  	errMsgTemplate      = "%s filter error (%s)"
    41  	multiErrMsgTemplate = "%s filter errors [%s]"
    42  )
    43  
    44  // Filter filters the event with dependency's defined filters
    45  func Filter(event *v1alpha1.Event, filter *v1alpha1.EventDependencyFilter, filtersLogicalOperator v1alpha1.LogicalOperator) (bool, error) {
    46  	if filter == nil {
    47  		return true, nil
    48  	}
    49  
    50  	ok, err := filterEvent(filter, filtersLogicalOperator, event)
    51  	if err != nil {
    52  		return false, err
    53  	}
    54  
    55  	return ok, nil
    56  }
    57  
    58  // filterEvent applies the filters to an Event
    59  func filterEvent(filter *v1alpha1.EventDependencyFilter, operator v1alpha1.LogicalOperator, event *v1alpha1.Event) (bool, error) {
    60  	var errMessages []string
    61  	if operator == v1alpha1.OrLogicalOperator {
    62  		errMessages = make([]string, 0)
    63  	}
    64  
    65  	exprFilter, exprErr := filterExpr(filter.Exprs, filter.ExprLogicalOperator, event)
    66  	if exprErr != nil {
    67  		if operator != v1alpha1.OrLogicalOperator {
    68  			return false, exprErr
    69  		}
    70  		errMessages = append(errMessages, exprErr.Error())
    71  	}
    72  
    73  	dataFilter, dataErr := filterData(filter.Data, filter.DataLogicalOperator, event)
    74  	if dataErr != nil {
    75  		if operator != v1alpha1.OrLogicalOperator {
    76  			return false, dataErr
    77  		}
    78  		errMessages = append(errMessages, dataErr.Error())
    79  	}
    80  
    81  	ctxFilter := filterContext(filter.Context, event.Context)
    82  
    83  	timeFilter, timeErr := filterTime(filter.Time, event.Context.Time.Time)
    84  	if timeErr != nil {
    85  		if operator != v1alpha1.OrLogicalOperator {
    86  			return false, timeErr
    87  		}
    88  		errMessages = append(errMessages, timeErr.Error())
    89  	}
    90  
    91  	scriptFilter, err := filterScript(filter.Script, event)
    92  	if err != nil {
    93  		return false, err
    94  	}
    95  
    96  	if operator == v1alpha1.OrLogicalOperator {
    97  		pass := (filter.Exprs != nil && exprFilter) ||
    98  			(filter.Data != nil && dataFilter) ||
    99  			(filter.Context != nil && ctxFilter) ||
   100  			(filter.Time != nil && timeFilter) ||
   101  			(filter.Script != "" && scriptFilter)
   102  
   103  		if len(errMessages) > 0 {
   104  			return pass, errors.New(strings.Join(errMessages, errMsgListSeparator))
   105  		}
   106  		return pass, nil
   107  	}
   108  	return exprFilter && dataFilter && ctxFilter && timeFilter && scriptFilter, nil
   109  }
   110  
   111  // filterExpr applies expression based filters against event data
   112  // expression evaluation is based on https://github.com/Knetic/govaluate
   113  // in case "operator input" is equal to v1alpha1.OrLogicalOperator, filters are evaluated as mutual exclusive
   114  func filterExpr(filters []v1alpha1.ExprFilter, operator v1alpha1.LogicalOperator, event *v1alpha1.Event) (bool, error) {
   115  	if filters == nil {
   116  		return true, nil
   117  	}
   118  	if event == nil {
   119  		return false, fmt.Errorf(errMsgTemplate, "expr", "nil event")
   120  	}
   121  	payload := event.Data
   122  	if payload == nil {
   123  		return true, nil
   124  	}
   125  	if !gjson.Valid(string(payload)) {
   126  		return false, fmt.Errorf(errMsgTemplate, "expr", "event data not valid JSON")
   127  	}
   128  
   129  	var errMessages []string
   130  	if operator == v1alpha1.OrLogicalOperator {
   131  		errMessages = make([]string, 0)
   132  	}
   133  filterExpr:
   134  	for _, filter := range filters {
   135  		parameters := map[string]interface{}{}
   136  		for _, field := range filter.Fields {
   137  			pathResult := gjson.GetBytes(payload, field.Path)
   138  			if !pathResult.Exists() {
   139  				errMsg := "path '%s' does not exist"
   140  				if operator == v1alpha1.OrLogicalOperator {
   141  					errMessages = append(errMessages, fmt.Sprintf(errMsg, field.Path))
   142  					continue filterExpr
   143  				} else {
   144  					return false, fmt.Errorf(errMsgTemplate, "expr", fmt.Sprintf(errMsg, field.Path))
   145  				}
   146  			}
   147  			parameters[field.Name] = pathResult.Value()
   148  		}
   149  
   150  		if len(parameters) == 0 {
   151  			continue
   152  		}
   153  
   154  		expr, exprErr := govaluate.NewEvaluableExpression(filter.Expr)
   155  		if exprErr != nil {
   156  			if operator == v1alpha1.OrLogicalOperator {
   157  				errMessages = append(errMessages, exprErr.Error())
   158  				continue
   159  			} else {
   160  				return false, fmt.Errorf(errMsgTemplate, "expr", exprErr.Error())
   161  			}
   162  		}
   163  
   164  		result, resErr := expr.Evaluate(parameters)
   165  		if resErr != nil {
   166  			if operator == v1alpha1.OrLogicalOperator {
   167  				errMessages = append(errMessages, resErr.Error())
   168  				continue
   169  			} else {
   170  				return false, fmt.Errorf(errMsgTemplate, "expr", resErr.Error())
   171  			}
   172  		}
   173  
   174  		if result == true {
   175  			if operator == v1alpha1.OrLogicalOperator {
   176  				return true, nil
   177  			}
   178  		} else {
   179  			if operator != v1alpha1.OrLogicalOperator {
   180  				return false, nil
   181  			}
   182  		}
   183  	}
   184  
   185  	if operator == v1alpha1.OrLogicalOperator {
   186  		if len(errMessages) > 0 {
   187  			return false, fmt.Errorf(multiErrMsgTemplate, "expr", strings.Join(errMessages, errMsgListSeparator))
   188  		}
   189  		return false, nil
   190  	} else {
   191  		return true, nil
   192  	}
   193  }
   194  
   195  // filterData runs the dataFilter against the Event's data
   196  // returns (true, nil) when data passes filters, false otherwise
   197  // in case "operator input" is equal to v1alpha1.OrLogicalOperator, filters are evaluated as mutual exclusive
   198  func filterData(filters []v1alpha1.DataFilter, operator v1alpha1.LogicalOperator, event *v1alpha1.Event) (bool, error) {
   199  	if len(filters) == 0 {
   200  		return true, nil
   201  	}
   202  	if event == nil {
   203  		return false, fmt.Errorf(errMsgTemplate, "data", "nil Event")
   204  	}
   205  	payload := event.Data
   206  	if payload == nil {
   207  		return true, nil
   208  	}
   209  	if !gjson.Valid(string(payload)) {
   210  		return false, fmt.Errorf(errMsgTemplate, "data", "event data not valid JSON")
   211  	}
   212  
   213  	var errMessages []string
   214  	if operator == v1alpha1.OrLogicalOperator {
   215  		errMessages = make([]string, 0)
   216  	}
   217  filterData:
   218  	for _, f := range filters {
   219  		pathResult := gjson.GetBytes(payload, f.Path)
   220  		if !pathResult.Exists() {
   221  			errMsg := "path '%s' does not exist"
   222  			if operator == v1alpha1.OrLogicalOperator {
   223  				errMessages = append(errMessages, fmt.Sprintf(errMsg, f.Path))
   224  				continue
   225  			} else {
   226  				return false, fmt.Errorf(errMsgTemplate, "data", fmt.Sprintf(errMsg, f.Path))
   227  			}
   228  		}
   229  
   230  		if f.Value == nil || len(f.Value) == 0 {
   231  			errMsg := "no values specified"
   232  			if operator == v1alpha1.OrLogicalOperator {
   233  				errMessages = append(errMessages, errMsg)
   234  				continue
   235  			} else {
   236  				return false, fmt.Errorf(errMsgTemplate, "data", errMsg)
   237  			}
   238  		}
   239  
   240  		if f.Template != "" {
   241  			tpl, tplErr := template.New("param").Funcs(sprig.FuncMap()).Parse(f.Template)
   242  			if tplErr != nil {
   243  				if operator == v1alpha1.OrLogicalOperator {
   244  					errMessages = append(errMessages, tplErr.Error())
   245  					continue
   246  				} else {
   247  					return false, fmt.Errorf(errMsgTemplate, "data", tplErr.Error())
   248  				}
   249  			}
   250  
   251  			var buf bytes.Buffer
   252  			execErr := tpl.Execute(&buf, map[string]interface{}{
   253  				"Input": pathResult.String(),
   254  			})
   255  			if execErr != nil {
   256  				if operator == v1alpha1.OrLogicalOperator {
   257  					errMessages = append(errMessages, execErr.Error())
   258  					continue
   259  				} else {
   260  					return false, fmt.Errorf(errMsgTemplate, "data", execErr.Error())
   261  				}
   262  			}
   263  
   264  			out := buf.String()
   265  			if out == "" || out == "<no value>" {
   266  				if operator == v1alpha1.OrLogicalOperator {
   267  					errMessages = append(errMessages, fmt.Sprintf("template evaluated to empty string or no value: '%s'", f.Template))
   268  					continue
   269  				} else {
   270  					return false, fmt.Errorf(errMsgTemplate, "data",
   271  						fmt.Sprintf("template '%s' evaluated to empty string or no value", f.Template))
   272  				}
   273  			}
   274  
   275  			pathResult = gjson.Parse(strconv.Quote(out))
   276  		}
   277  
   278  		switch f.Type {
   279  		case v1alpha1.JSONTypeBool:
   280  			for _, value := range f.Value {
   281  				val, err := strconv.ParseBool(value)
   282  				if err != nil {
   283  					if operator == v1alpha1.OrLogicalOperator {
   284  						errMessages = append(errMessages, err.Error())
   285  						continue filterData
   286  					} else {
   287  						return false, fmt.Errorf(errMsgTemplate, "data", err.Error())
   288  					}
   289  				}
   290  
   291  				if val == pathResult.Bool() {
   292  					if operator == v1alpha1.OrLogicalOperator {
   293  						return true, nil
   294  					} else {
   295  						continue filterData
   296  					}
   297  				}
   298  			}
   299  
   300  			if operator == v1alpha1.OrLogicalOperator {
   301  				continue filterData
   302  			} else {
   303  				return false, nil
   304  			}
   305  
   306  		case v1alpha1.JSONTypeNumber:
   307  			for _, value := range f.Value {
   308  				filterVal, err := strconv.ParseFloat(value, 64)
   309  				eventVal := pathResult.Float()
   310  				if err != nil {
   311  					if operator == v1alpha1.OrLogicalOperator {
   312  						errMessages = append(errMessages, err.Error())
   313  						continue filterData
   314  					} else {
   315  						return false, fmt.Errorf(errMsgTemplate, "data", err.Error())
   316  					}
   317  				}
   318  
   319  				compareResult := false
   320  				switch f.Comparator {
   321  				case v1alpha1.GreaterThanOrEqualTo:
   322  					if eventVal >= filterVal {
   323  						compareResult = true
   324  					}
   325  				case v1alpha1.GreaterThan:
   326  					if eventVal > filterVal {
   327  						compareResult = true
   328  					}
   329  				case v1alpha1.LessThan:
   330  					if eventVal < filterVal {
   331  						compareResult = true
   332  					}
   333  				case v1alpha1.LessThanOrEqualTo:
   334  					if eventVal <= filterVal {
   335  						compareResult = true
   336  					}
   337  				case v1alpha1.NotEqualTo:
   338  					if eventVal != filterVal {
   339  						compareResult = true
   340  					}
   341  				case v1alpha1.EqualTo, v1alpha1.EmptyComparator:
   342  					if eventVal == filterVal {
   343  						compareResult = true
   344  					}
   345  				}
   346  
   347  				if compareResult {
   348  					if operator == v1alpha1.OrLogicalOperator {
   349  						return true, nil
   350  					} else {
   351  						continue filterData
   352  					}
   353  				}
   354  			}
   355  			if operator == v1alpha1.OrLogicalOperator {
   356  				continue filterData
   357  			} else {
   358  				return false, nil
   359  			}
   360  
   361  		case v1alpha1.JSONTypeString:
   362  			for _, value := range f.Value {
   363  				exp, err := regexp.Compile(value)
   364  				if err != nil {
   365  					if operator == v1alpha1.OrLogicalOperator {
   366  						errMessages = append(errMessages, err.Error())
   367  						continue filterData
   368  					} else {
   369  						return false, fmt.Errorf(errMsgTemplate, "data", err.Error())
   370  					}
   371  				}
   372  
   373  				matchResult := false
   374  				match := exp.Match([]byte(pathResult.String()))
   375  				switch f.Comparator {
   376  				case v1alpha1.EqualTo, v1alpha1.EmptyComparator:
   377  					if match {
   378  						matchResult = true
   379  					}
   380  				case v1alpha1.NotEqualTo:
   381  					if !match {
   382  						matchResult = true
   383  					}
   384  				}
   385  
   386  				if matchResult {
   387  					if operator == v1alpha1.OrLogicalOperator {
   388  						return true, nil
   389  					} else {
   390  						continue filterData
   391  					}
   392  				}
   393  			}
   394  
   395  			if operator == v1alpha1.OrLogicalOperator {
   396  				continue filterData
   397  			} else {
   398  				return false, nil
   399  			}
   400  
   401  		default:
   402  			errMsg := "unsupported JSON type '%s'"
   403  			if operator == v1alpha1.OrLogicalOperator {
   404  				errMessages = append(errMessages, fmt.Sprintf(errMsg, f.Type))
   405  				continue filterData
   406  			} else {
   407  				return false, fmt.Errorf(errMsgTemplate, "data", fmt.Sprintf(errMsg, f.Type))
   408  			}
   409  		}
   410  	}
   411  
   412  	if operator == v1alpha1.OrLogicalOperator {
   413  		if len(errMessages) > 0 {
   414  			return false, fmt.Errorf(multiErrMsgTemplate, "data", strings.Join(errMessages, errMsgListSeparator))
   415  		}
   416  		return false, nil
   417  	} else {
   418  		return true, nil
   419  	}
   420  }
   421  
   422  // filterContext checks the expectedResult EventContext against the actual EventContext
   423  // values are only enforced if they are non-zero values
   424  // map types check that the expectedResult map is a subset of the actual map
   425  func filterContext(expected *v1alpha1.EventContext, actual *v1alpha1.EventContext) bool {
   426  	if expected == nil {
   427  		return true
   428  	}
   429  	if actual == nil {
   430  		return false
   431  	}
   432  
   433  	res := true
   434  	if expected.Type != "" {
   435  		res = res && expected.Type == actual.Type
   436  	}
   437  	if expected.Subject != "" {
   438  		res = res && expected.Subject == actual.Subject
   439  	}
   440  	if expected.Source != "" {
   441  		res = res && expected.Source == actual.Source
   442  	}
   443  	if expected.DataContentType != "" {
   444  		res = res && expected.DataContentType == actual.DataContentType
   445  	}
   446  	return res
   447  }
   448  
   449  // filterTime checks the eventTime falls into time range specified by the timeFilter.
   450  // Start is inclusive, and Stop is exclusive.
   451  //
   452  // if Start < Stop: eventTime must be in [Start, Stop)
   453  //
   454  //	0:00        Start       Stop        0:00
   455  //	├───────────●───────────○───────────┤
   456  //	            └─── OK ────┘
   457  //
   458  // if Stop < Start: eventTime must be in [Start, Stop@Next day)
   459  //
   460  // this is equivalent to: eventTime must be in [0:00, Stop) or [Start, 0:00@Next day)
   461  //
   462  //	0:00                    Start       0:00       Stop                     0:00
   463  //	├───────────○───────────●───────────┼───────────○───────────●───────────┤
   464  //	                        └───────── OK ──────────┘
   465  //
   466  //	0:00        Stop        Start       0:00
   467  //	●───────────○───────────●───────────○
   468  //	└─── OK ────┘           └─── OK ────┘
   469  func filterTime(timeFilter *v1alpha1.TimeFilter, eventTime time.Time) (bool, error) {
   470  	if timeFilter == nil {
   471  		return true, nil
   472  	}
   473  
   474  	// Parse start and stop
   475  	startTime, startErr := common.ParseTime(timeFilter.Start, eventTime)
   476  	if startErr != nil {
   477  		return false, fmt.Errorf(errMsgTemplate, "time", startErr.Error())
   478  	}
   479  	stopTime, stopErr := common.ParseTime(timeFilter.Stop, eventTime)
   480  	if stopErr != nil {
   481  		return false, fmt.Errorf(errMsgTemplate, "time", stopErr.Error())
   482  	}
   483  
   484  	// Filtering logic
   485  	if startTime.Before(stopTime) {
   486  		return (eventTime.After(startTime) || eventTime.Equal(startTime)) && eventTime.Before(stopTime), nil
   487  	} else {
   488  		return (eventTime.After(startTime) || eventTime.Equal(startTime)) || eventTime.Before(stopTime), nil
   489  	}
   490  }
   491  
   492  func filterScript(script string, event *v1alpha1.Event) (bool, error) {
   493  	if script == "" {
   494  		return true, nil
   495  	}
   496  	if event == nil {
   497  		return false, fmt.Errorf("nil event")
   498  	}
   499  	payload := event.Data
   500  	if payload == nil {
   501  		return true, nil
   502  	}
   503  	var js *json.RawMessage
   504  	if err := json.Unmarshal(payload, &js); err != nil {
   505  		return false, err
   506  	}
   507  	var jsData []byte
   508  	jsData, err := json.Marshal(js)
   509  	if err != nil {
   510  		return false, err
   511  	}
   512  	l := lua.NewState()
   513  	defer l.Close()
   514  	var payloadJson map[string]interface{}
   515  	if err = json.Unmarshal(jsData, &payloadJson); err != nil {
   516  		return false, err
   517  	}
   518  	lEvent := mapToTable(payloadJson)
   519  	l.SetGlobal("event", lEvent)
   520  	if err = l.DoString(script); err != nil {
   521  		return false, err
   522  	}
   523  	lv := l.Get(-1)
   524  	return lv == lua.LTrue, nil
   525  }