bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/pkg/database/alerts.go (about)

     1  package database
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"bitbucket.org/Aishee/synsec/pkg/database/ent"
    11  	"bitbucket.org/Aishee/synsec/pkg/database/ent/alert"
    12  	"bitbucket.org/Aishee/synsec/pkg/database/ent/decision"
    13  	"bitbucket.org/Aishee/synsec/pkg/database/ent/event"
    14  	"bitbucket.org/Aishee/synsec/pkg/database/ent/meta"
    15  	"bitbucket.org/Aishee/synsec/pkg/models"
    16  	"bitbucket.org/Aishee/synsec/pkg/types"
    17  	"github.com/davecgh/go-spew/spew"
    18  	"github.com/pkg/errors"
    19  	log "github.com/sirupsen/logrus"
    20  )
    21  
    22  const (
    23  	paginationSize = 100 // used to queryAlert to avoid 'too many SQL variable'
    24  	defaultLimit   = 100 // default limit of element to returns when query alerts
    25  	bulkSize       = 50  // bulk size when create alerts
    26  )
    27  
    28  func formatAlertAsString(machineId string, alert *models.Alert) []string {
    29  	var retStr []string
    30  
    31  	/**/
    32  	src := ""
    33  	if alert.Source != nil {
    34  		if *alert.Source.Scope == types.Ip {
    35  			src = fmt.Sprintf("ip %s", *alert.Source.Value)
    36  			if alert.Source.Cn != "" {
    37  				src += " (" + alert.Source.Cn
    38  				if alert.Source.AsNumber != "" {
    39  					src += "/" + alert.Source.AsNumber
    40  				}
    41  				src += ")"
    42  			}
    43  		} else if *alert.Source.Scope == types.Range {
    44  			src = fmt.Sprintf("range %s", *alert.Source.Value)
    45  			if alert.Source.Cn != "" {
    46  				src += " (" + alert.Source.Cn
    47  				if alert.Source.AsNumber != "" {
    48  					src += "/" + alert.Source.AsNumber
    49  				}
    50  				src += ")"
    51  			}
    52  		} else {
    53  			src = fmt.Sprintf("%s %s", *alert.Source.Scope, *alert.Source.Value)
    54  		}
    55  	} else {
    56  		src = "empty source"
    57  	}
    58  
    59  	/**/
    60  	reason := ""
    61  	if *alert.Scenario != "" {
    62  		reason = fmt.Sprintf("%s by %s", *alert.Scenario, src)
    63  	} else if *alert.Message != "" {
    64  		reason = fmt.Sprintf("%s by %s", *alert.Scenario, src)
    65  	} else {
    66  		reason = fmt.Sprintf("empty scenario by %s", src)
    67  	}
    68  
    69  	if len(alert.Decisions) > 0 {
    70  		for _, decisionItem := range alert.Decisions {
    71  			decision := ""
    72  			if alert.Simulated != nil && *alert.Simulated {
    73  				decision = "(simulated alert)"
    74  			} else if decisionItem.Simulated != nil && *decisionItem.Simulated {
    75  				decision = "(simulated decision)"
    76  			}
    77  			if log.GetLevel() >= log.DebugLevel {
    78  				/*spew is expensive*/
    79  				log.Debugf("%s", spew.Sdump(decisionItem))
    80  			}
    81  			decision += fmt.Sprintf("%s %s on %s %s", *decisionItem.Duration,
    82  				*decisionItem.Type, *decisionItem.Scope, *decisionItem.Value)
    83  			retStr = append(retStr,
    84  				fmt.Sprintf("(%s/%s) %s : %s", machineId,
    85  					*decisionItem.Origin, reason, decision))
    86  		}
    87  	} else {
    88  		retStr = append(retStr, fmt.Sprintf("(%s) alert : %s", machineId, reason))
    89  	}
    90  	return retStr
    91  }
    92  
    93  func (c *Client) CreateAlert(machineID string, alertList []*models.Alert) ([]string, error) {
    94  	pageStart := 0
    95  	pageEnd := bulkSize
    96  	ret := []string{}
    97  	for {
    98  		if pageEnd >= len(alertList) {
    99  			results, err := c.CreateAlertBulk(machineID, alertList[pageStart:])
   100  			if err != nil {
   101  				return []string{}, fmt.Errorf("unable to create alerts: %s", err)
   102  			}
   103  			ret = append(ret, results...)
   104  			break
   105  		}
   106  		results, err := c.CreateAlertBulk(machineID, alertList[pageStart:pageEnd])
   107  		if err != nil {
   108  			return []string{}, fmt.Errorf("unable to create alerts: %s", err)
   109  		}
   110  		ret = append(ret, results...)
   111  		pageStart += bulkSize
   112  		pageEnd += bulkSize
   113  	}
   114  	return ret, nil
   115  }
   116  
   117  func (c *Client) CreateAlertBulk(machineId string, alertList []*models.Alert) ([]string, error) {
   118  
   119  	ret := []string{}
   120  	bulkSize := 20
   121  
   122  	c.Log.Debugf("writting %d items", len(alertList))
   123  	bulk := make([]*ent.AlertCreate, 0, bulkSize)
   124  	for i, alertItem := range alertList {
   125  		var decisions []*ent.Decision
   126  		var metas []*ent.Meta
   127  		var events []*ent.Event
   128  
   129  		owner, err := c.QueryMachineByID(machineId)
   130  		if err != nil {
   131  			if errors.Cause(err) != UserNotExists {
   132  				return []string{}, errors.Wrapf(QueryFail, "machine '%s': %s", alertItem.MachineID, err)
   133  			}
   134  			c.Log.Debugf("CreateAlertBulk: Machine Id %s doesn't exist", machineId)
   135  			owner = nil
   136  		}
   137  		startAtTime, err := time.Parse(time.RFC3339, *alertItem.StartAt)
   138  		if err != nil {
   139  			return []string{}, errors.Wrapf(ParseTimeFail, "start_at field time '%s': %s", *alertItem.StartAt, err)
   140  		}
   141  
   142  		stopAtTime, err := time.Parse(time.RFC3339, *alertItem.StopAt)
   143  		if err != nil {
   144  			return []string{}, errors.Wrapf(ParseTimeFail, "stop_at field time '%s': %s", *alertItem.StopAt, err)
   145  		}
   146  		/*display proper alert in logs*/
   147  		for _, disp := range formatAlertAsString(machineId, alertItem) {
   148  			c.Log.Info(disp)
   149  		}
   150  
   151  		if len(alertItem.Events) > 0 {
   152  			eventBulk := make([]*ent.EventCreate, len(alertItem.Events))
   153  			for i, eventItem := range alertItem.Events {
   154  				ts, err := time.Parse(time.RFC3339, *eventItem.Timestamp)
   155  				if err != nil {
   156  					return []string{}, errors.Wrapf(ParseTimeFail, "event timestamp '%s' : %s", *eventItem.Timestamp, err)
   157  				}
   158  				marshallMetas, err := json.Marshal(eventItem.Meta)
   159  				if err != nil {
   160  					return []string{}, errors.Wrapf(MarshalFail, "event meta '%v' : %s", eventItem.Meta, err)
   161  				}
   162  
   163  				eventBulk[i] = c.Ent.Event.Create().
   164  					SetTime(ts).
   165  					SetSerialized(string(marshallMetas))
   166  			}
   167  			events, err = c.Ent.Event.CreateBulk(eventBulk...).Save(c.CTX)
   168  			if err != nil {
   169  				return []string{}, errors.Wrapf(BulkError, "creating alert events: %s", err)
   170  			}
   171  		}
   172  
   173  		if len(alertItem.Meta) > 0 {
   174  			metaBulk := make([]*ent.MetaCreate, len(alertItem.Meta))
   175  			for i, metaItem := range alertItem.Meta {
   176  				metaBulk[i] = c.Ent.Meta.Create().
   177  					SetKey(metaItem.Key).
   178  					SetValue(metaItem.Value)
   179  			}
   180  			metas, err = c.Ent.Meta.CreateBulk(metaBulk...).Save(c.CTX)
   181  			if err != nil {
   182  				return []string{}, errors.Wrapf(BulkError, "creating alert meta: %s", err)
   183  			}
   184  		}
   185  
   186  		ts, err := time.Parse(time.RFC3339, *alertItem.StopAt)
   187  		if err != nil {
   188  			c.Log.Errorf("While parsing StartAt of item %s : %s", *alertItem.StopAt, err)
   189  			ts = time.Now()
   190  		}
   191  		if len(alertItem.Decisions) > 0 {
   192  			decisionBulk := make([]*ent.DecisionCreate, len(alertItem.Decisions))
   193  			for i, decisionItem := range alertItem.Decisions {
   194  				var start_ip, start_sfx, end_ip, end_sfx int64
   195  				var sz int
   196  
   197  				duration, err := time.ParseDuration(*decisionItem.Duration)
   198  				if err != nil {
   199  					return []string{}, errors.Wrapf(ParseDurationFail, "decision duration '%v' : %s", decisionItem.Duration, err)
   200  				}
   201  
   202  				/*if the scope is IP or Range, convert the value to integers */
   203  				if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" {
   204  					sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value)
   205  					if err != nil {
   206  						return []string{}, errors.Wrapf(ParseDurationFail, "invalid addr/range %s : %s", *decisionItem.Value, err)
   207  					}
   208  				}
   209  				decisionBulk[i] = c.Ent.Decision.Create().
   210  					SetUntil(ts.Add(duration)).
   211  					SetScenario(*decisionItem.Scenario).
   212  					SetType(*decisionItem.Type).
   213  					SetStartIP(start_ip).
   214  					SetStartSuffix(start_sfx).
   215  					SetEndIP(end_ip).
   216  					SetEndSuffix(end_sfx).
   217  					SetIPSize(int64(sz)).
   218  					SetValue(*decisionItem.Value).
   219  					SetScope(*decisionItem.Scope).
   220  					SetOrigin(*decisionItem.Origin).
   221  					SetSimulated(*alertItem.Simulated)
   222  			}
   223  			decisions, err = c.Ent.Decision.CreateBulk(decisionBulk...).Save(c.CTX)
   224  			if err != nil {
   225  				return []string{}, errors.Wrapf(BulkError, "creating alert decisions: %s", err)
   226  
   227  			}
   228  		}
   229  
   230  		alertB := c.Ent.Alert.
   231  			Create().
   232  			SetScenario(*alertItem.Scenario).
   233  			SetMessage(*alertItem.Message).
   234  			SetEventsCount(*alertItem.EventsCount).
   235  			SetStartedAt(startAtTime).
   236  			SetStoppedAt(stopAtTime).
   237  			SetSourceScope(*alertItem.Source.Scope).
   238  			SetSourceValue(*alertItem.Source.Value).
   239  			SetSourceIp(alertItem.Source.IP).
   240  			SetSourceRange(alertItem.Source.Range).
   241  			SetSourceAsNumber(alertItem.Source.AsNumber).
   242  			SetSourceAsName(alertItem.Source.AsName).
   243  			SetSourceCountry(alertItem.Source.Cn).
   244  			SetSourceLatitude(alertItem.Source.Latitude).
   245  			SetSourceLongitude(alertItem.Source.Longitude).
   246  			SetCapacity(*alertItem.Capacity).
   247  			SetLeakSpeed(*alertItem.Leakspeed).
   248  			SetSimulated(*alertItem.Simulated).
   249  			SetScenarioVersion(*alertItem.ScenarioVersion).
   250  			SetScenarioHash(*alertItem.ScenarioHash).
   251  			AddDecisions(decisions...).
   252  			AddEvents(events...).
   253  			AddMetas(metas...)
   254  
   255  		if owner != nil {
   256  			alertB.SetOwner(owner)
   257  		}
   258  		bulk = append(bulk, alertB)
   259  
   260  		if len(bulk) == bulkSize {
   261  			alerts, err := c.Ent.Alert.CreateBulk(bulk...).Save(c.CTX)
   262  			if err != nil {
   263  				return []string{}, errors.Wrapf(BulkError, "bulk creating alert : %s", err)
   264  			}
   265  			for _, alert := range alerts {
   266  				ret = append(ret, strconv.Itoa(alert.ID))
   267  			}
   268  
   269  			if len(alertList)-i <= bulkSize {
   270  				bulk = make([]*ent.AlertCreate, 0, (len(alertList) - i))
   271  			} else {
   272  				bulk = make([]*ent.AlertCreate, 0, bulkSize)
   273  			}
   274  		}
   275  	}
   276  
   277  	alerts, err := c.Ent.Alert.CreateBulk(bulk...).Save(c.CTX)
   278  	if err != nil {
   279  		return []string{}, errors.Wrapf(BulkError, "leftovers creating alert : %s", err)
   280  	}
   281  
   282  	for _, alert := range alerts {
   283  		ret = append(ret, strconv.Itoa(alert.ID))
   284  	}
   285  
   286  	return ret, nil
   287  }
   288  
   289  func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]string) (*ent.AlertQuery, error) {
   290  	var err error
   291  	var start_ip, start_sfx, end_ip, end_sfx int64
   292  	var hasActiveDecision bool
   293  	var ip_sz int
   294  	var contains bool = true
   295  	/*if contains is true, return bans that *contains* the given value (value is the inner)
   296  	  else, return bans that are *contained* by the given value (value is the outer)*/
   297  
   298  	/*the simulated filter is a bit different : if it's not present *or* set to false, specifically exclude records with simulated to true */
   299  	if v, ok := filter["simulated"]; ok {
   300  		if v[0] == "false" {
   301  			alerts = alerts.Where(alert.SimulatedEQ(false))
   302  		}
   303  		delete(filter, "simulated")
   304  	}
   305  
   306  	for param, value := range filter {
   307  		switch param {
   308  		case "contains":
   309  			contains, err = strconv.ParseBool(value[0])
   310  			if err != nil {
   311  				return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
   312  			}
   313  		case "scope":
   314  			var scope string = value[0]
   315  			if strings.ToLower(scope) == "ip" {
   316  				scope = types.Ip
   317  			} else if strings.ToLower(scope) == "range" {
   318  				scope = types.Range
   319  			}
   320  			alerts = alerts.Where(alert.SourceScopeEQ(scope))
   321  		case "value":
   322  			alerts = alerts.Where(alert.SourceValueEQ(value[0]))
   323  		case "scenario":
   324  			alerts = alerts.Where(alert.ScenarioEQ(value[0]))
   325  		case "ip", "range":
   326  			ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
   327  			if err != nil {
   328  				return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
   329  			}
   330  		case "since":
   331  			duration, err := types.ParseDuration(value[0])
   332  			if err != nil {
   333  				return nil, errors.Wrap(err, "while parsing duration")
   334  			}
   335  			since := time.Now().Add(-duration)
   336  			if since.IsZero() {
   337  				return nil, fmt.Errorf("Empty time now() - %s", since.String())
   338  			}
   339  			alerts = alerts.Where(alert.StartedAtGTE(since))
   340  		case "created_before":
   341  			duration, err := types.ParseDuration(value[0])
   342  			if err != nil {
   343  				return nil, errors.Wrap(err, "while parsing duration")
   344  			}
   345  			since := time.Now().Add(-duration)
   346  			if since.IsZero() {
   347  				return nil, fmt.Errorf("Empty time now() - %s", since.String())
   348  			}
   349  			alerts = alerts.Where(alert.CreatedAtLTE(since))
   350  		case "until":
   351  			duration, err := types.ParseDuration(value[0])
   352  			if err != nil {
   353  				return nil, errors.Wrap(err, "while parsing duration")
   354  			}
   355  			until := time.Now().Add(-duration)
   356  			if until.IsZero() {
   357  				return nil, fmt.Errorf("Empty time now() - %s", until.String())
   358  			}
   359  			alerts = alerts.Where(alert.StartedAtLTE(until))
   360  		case "decision_type":
   361  			alerts = alerts.Where(alert.HasDecisionsWith(decision.TypeEQ(value[0])))
   362  		case "has_active_decision":
   363  			if hasActiveDecision, err = strconv.ParseBool(value[0]); err != nil {
   364  				return nil, errors.Wrapf(ParseType, "'%s' is not a boolean: %s", value[0], err)
   365  			}
   366  			if hasActiveDecision {
   367  				alerts = alerts.Where(alert.HasDecisionsWith(decision.UntilGTE(time.Now())))
   368  			} else {
   369  				alerts = alerts.Where(alert.Not(alert.HasDecisions()))
   370  			}
   371  		case "limit":
   372  			continue
   373  		case "sort":
   374  			continue
   375  		default:
   376  			return nil, errors.Wrapf(InvalidFilter, "Filter parameter '%s' is unknown (=%s)", param, value[0])
   377  		}
   378  	}
   379  
   380  	if ip_sz == 4 {
   381  		if contains { /*decision contains {start_ip,end_ip}*/
   382  			alerts = alerts.Where(alert.And(
   383  				alert.HasDecisionsWith(decision.StartIPLTE(start_ip)),
   384  				alert.HasDecisionsWith(decision.EndIPGTE(end_ip)),
   385  				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
   386  			))
   387  		} else { /*decision is contained within {start_ip,end_ip}*/
   388  			alerts = alerts.Where(alert.And(
   389  				alert.HasDecisionsWith(decision.StartIPGTE(start_ip)),
   390  				alert.HasDecisionsWith(decision.EndIPLTE(end_ip)),
   391  				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
   392  			))
   393  		}
   394  	} else if ip_sz == 16 {
   395  
   396  		if contains { /*decision contains {start_ip,end_ip}*/
   397  			alerts = alerts.Where(alert.And(
   398  				//matching addr size
   399  				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
   400  				alert.Or(
   401  					//decision.start_ip < query.start_ip
   402  					alert.HasDecisionsWith(decision.StartIPLT(start_ip)),
   403  					alert.And(
   404  						//decision.start_ip == query.start_ip
   405  						alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
   406  						//decision.start_suffix <= query.start_suffix
   407  						alert.HasDecisionsWith(decision.StartSuffixLTE(start_sfx)),
   408  					)),
   409  				alert.Or(
   410  					//decision.end_ip > query.end_ip
   411  					alert.HasDecisionsWith(decision.EndIPGT(end_ip)),
   412  					alert.And(
   413  						//decision.end_ip == query.end_ip
   414  						alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
   415  						//decision.end_suffix >= query.end_suffix
   416  						alert.HasDecisionsWith(decision.EndSuffixGTE(end_sfx)),
   417  					),
   418  				),
   419  			))
   420  		} else { /*decision is contained within {start_ip,end_ip}*/
   421  			alerts = alerts.Where(alert.And(
   422  				//matching addr size
   423  				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
   424  				alert.Or(
   425  					//decision.start_ip > query.start_ip
   426  					alert.HasDecisionsWith(decision.StartIPGT(start_ip)),
   427  					alert.And(
   428  						//decision.start_ip == query.start_ip
   429  						alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
   430  						//decision.start_suffix >= query.start_suffix
   431  						alert.HasDecisionsWith(decision.StartSuffixGTE(start_sfx)),
   432  					)),
   433  				alert.Or(
   434  					//decision.end_ip < query.end_ip
   435  					alert.HasDecisionsWith(decision.EndIPLT(end_ip)),
   436  					alert.And(
   437  						//decision.end_ip == query.end_ip
   438  						alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
   439  						//decision.end_suffix <= query.end_suffix
   440  						alert.HasDecisionsWith(decision.EndSuffixLTE(end_sfx)),
   441  					),
   442  				),
   443  			))
   444  		}
   445  	} else if ip_sz != 0 {
   446  		return nil, errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz)
   447  	}
   448  	return alerts, nil
   449  }
   450  
   451  func (c *Client) TotalAlerts() (int, error) {
   452  	return c.Ent.Alert.Query().Count(c.CTX)
   453  }
   454  
   455  func (c *Client) QueryAlertWithFilter(filter map[string][]string) ([]*ent.Alert, error) {
   456  	sort := "DESC" // we sort by desc by default
   457  	if val, ok := filter["sort"]; ok {
   458  		if val[0] != "ASC" && val[0] != "DESC" {
   459  			c.Log.Errorf("invalid 'sort' parameter: %s", val)
   460  		} else {
   461  			sort = val[0]
   462  		}
   463  	}
   464  	limit := defaultLimit
   465  	if val, ok := filter["limit"]; ok {
   466  		limitConv, err := strconv.Atoi(val[0])
   467  		if err != nil {
   468  			return []*ent.Alert{}, errors.Wrapf(QueryFail, "bad limit in parameters: %s", val)
   469  		}
   470  		limit = limitConv
   471  
   472  	}
   473  	offset := 0
   474  	ret := make([]*ent.Alert, 0)
   475  	for {
   476  		alerts := c.Ent.Alert.Query()
   477  		alerts, err := BuildAlertRequestFromFilter(alerts, filter)
   478  		if err != nil {
   479  			return []*ent.Alert{}, err
   480  		}
   481  		alerts = alerts.
   482  			WithDecisions().
   483  			WithEvents().
   484  			WithMetas().
   485  			WithOwner()
   486  		if sort == "ASC" {
   487  			alerts = alerts.Order(ent.Asc(alert.FieldCreatedAt))
   488  		} else {
   489  			alerts = alerts.Order(ent.Desc(alert.FieldCreatedAt))
   490  		}
   491  		if limit == 0 {
   492  			limit, err = alerts.Count(c.CTX)
   493  			if err != nil {
   494  				return []*ent.Alert{}, fmt.Errorf("unable to count nb alerts: %s", err)
   495  			}
   496  		}
   497  		result, err := alerts.Limit(paginationSize).Offset(offset).All(c.CTX)
   498  		if err != nil {
   499  			return []*ent.Alert{}, errors.Wrapf(QueryFail, "pagination size: %d, offset: %d: %s", paginationSize, offset, err)
   500  		}
   501  		if diff := limit - len(ret); diff < paginationSize {
   502  			if len(result) < diff {
   503  				ret = append(ret, result...)
   504  				c.Log.Debugf("Pagination done, %d < %d", len(result), diff)
   505  				break
   506  			}
   507  			ret = append(ret, result[0:diff]...)
   508  		} else {
   509  			ret = append(ret, result...)
   510  		}
   511  		if len(ret) == limit || len(ret) == 0 {
   512  			c.Log.Debugf("Pagination done len(ret) = %d", len(ret))
   513  			break
   514  		}
   515  		offset += paginationSize
   516  	}
   517  
   518  	return ret, nil
   519  }
   520  
   521  func (c *Client) DeleteAlertGraph(alertItem *ent.Alert) error {
   522  	// delete the associated events
   523  	_, err := c.Ent.Event.Delete().
   524  		Where(event.HasOwnerWith(alert.IDEQ(alertItem.ID))).Exec(c.CTX)
   525  	if err != nil {
   526  		c.Log.Warningf("DeleteAlertGraph : %s", err)
   527  		return errors.Wrapf(DeleteFail, "event with alert ID '%d'", alertItem.ID)
   528  	}
   529  
   530  	// delete the associated meta
   531  	_, err = c.Ent.Meta.Delete().
   532  		Where(meta.HasOwnerWith(alert.IDEQ(alertItem.ID))).Exec(c.CTX)
   533  	if err != nil {
   534  		c.Log.Warningf("DeleteAlertGraph : %s", err)
   535  		return errors.Wrapf(DeleteFail, "meta with alert ID '%d'", alertItem.ID)
   536  	}
   537  
   538  	// delete the associated decisions
   539  	_, err = c.Ent.Decision.Delete().
   540  		Where(decision.HasOwnerWith(alert.IDEQ(alertItem.ID))).Exec(c.CTX)
   541  	if err != nil {
   542  		c.Log.Warningf("DeleteAlertGraph : %s", err)
   543  		return errors.Wrapf(DeleteFail, "decision with alert ID '%d'", alertItem.ID)
   544  	}
   545  
   546  	// delete the alert
   547  	err = c.Ent.Alert.DeleteOne(alertItem).Exec(c.CTX)
   548  	if err != nil {
   549  		c.Log.Warningf("DeleteAlertGraph : %s", err)
   550  		return errors.Wrapf(DeleteFail, "alert with ID '%d'", alertItem.ID)
   551  	}
   552  
   553  	return nil
   554  }
   555  
   556  func (c *Client) DeleteAlertWithFilter(filter map[string][]string) (int, error) {
   557  	var err error
   558  
   559  	// Get all the alerts that match the filter
   560  	alertsToDelete, err := c.QueryAlertWithFilter(filter)
   561  
   562  	for _, alertItem := range alertsToDelete {
   563  		err = c.DeleteAlertGraph(alertItem)
   564  		if err != nil {
   565  			c.Log.Warningf("DeleteAlertWithFilter : %s", err)
   566  			return 0, errors.Wrapf(DeleteFail, "event with alert ID '%d'", alertItem.ID)
   567  		}
   568  	}
   569  	return len(alertsToDelete), nil
   570  }
   571  
   572  func (c *Client) FlushAlerts(MaxAge string, MaxItems int) error {
   573  	var deletedByAge int
   574  	var deletedByNbItem int
   575  	var totalAlerts int
   576  	var err error
   577  	totalAlerts, err = c.TotalAlerts()
   578  	if err != nil {
   579  		c.Log.Warningf("FlushAlerts (max items count) : %s", err)
   580  		return errors.Wrap(err, "unable to get alerts count")
   581  	}
   582  	if MaxAge != "" {
   583  		filter := map[string][]string{
   584  			"created_before": {MaxAge},
   585  		}
   586  		nbDeleted, err := c.DeleteAlertWithFilter(filter)
   587  		if err != nil {
   588  			c.Log.Warningf("FlushAlerts (max age) : %s", err)
   589  			return errors.Wrapf(err, "unable to flush alerts with filter until: %s", MaxAge)
   590  		}
   591  		deletedByAge = nbDeleted
   592  	}
   593  	if MaxItems > 0 {
   594  		if totalAlerts > MaxItems {
   595  			nbToDelete := totalAlerts - MaxItems
   596  			alerts, err := c.QueryAlertWithFilter(map[string][]string{
   597  				"sort":  {"ASC"},
   598  				"limit": {strconv.Itoa(nbToDelete)},
   599  			}) // we want to delete older alerts if we reach the max number of items
   600  			if err != nil {
   601  				c.Log.Warningf("FlushAlerts (max items query) : %s", err)
   602  				return errors.Wrap(err, "unable to get all alerts")
   603  			}
   604  			for itemNb, alert := range alerts {
   605  				if itemNb < nbToDelete {
   606  					err := c.DeleteAlertGraph(alert)
   607  					if err != nil {
   608  						c.Log.Warningf("FlushAlerts : %s", err)
   609  						return errors.Wrap(err, "unable to flush alert")
   610  					}
   611  					deletedByNbItem++
   612  				}
   613  			}
   614  		}
   615  	}
   616  	if deletedByNbItem > 0 {
   617  		c.Log.Infof("flushed %d/%d alerts because max number of alerts has been reached (%d max)", deletedByNbItem, totalAlerts, MaxItems)
   618  	}
   619  	if deletedByAge > 0 {
   620  		c.Log.Infof("flushed %d/%d alerts because they were created %s ago or more", deletedByAge, totalAlerts, MaxAge)
   621  	}
   622  	return nil
   623  }
   624  
   625  func (c *Client) GetAlertByID(alertID int) (*ent.Alert, error) {
   626  	alert, err := c.Ent.Alert.Query().Where(alert.IDEQ(alertID)).WithDecisions().WithEvents().WithMetas().WithOwner().First(c.CTX)
   627  	if err != nil {
   628  		/*record not found, 404*/
   629  		if ent.IsNotFound(err) {
   630  			log.Warningf("GetAlertByID (not found): %s", err)
   631  			return &ent.Alert{}, ItemNotFound
   632  		}
   633  		c.Log.Warningf("GetAlertByID : %s", err)
   634  		return &ent.Alert{}, QueryFail
   635  	}
   636  	return alert, nil
   637  }