package check import ( "encoding/json" "time" "github.com/influxdata/flux/ast" "github.com/influxdata/flux/parser" "github.com/influxdata/influxdb" "github.com/influxdata/influxdb/notification/flux" ) var _ influxdb.Check = &Custom{} // Custom is the custom check. type Custom struct { ID influxdb.ID `json:"id,omitempty"` Name string `json:"name"` Description string `json:"description,omitempty"` OwnerID influxdb.ID `json:"ownerID,omitempty"` OrgID influxdb.ID `json:"orgID,omitempty"` Query influxdb.DashboardQuery `json:"query"` TaskID influxdb.ID `json:"taskID,omitempty"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } // flux example for threshold check for reference: // package main // import "influxdata/influxdb/monitor" // import "influxdata/influxdb/v1" // data = from(bucket: "_tasks") // |> range(start: -1m) // |> filter(fn: (r) => r._measurement == "runs") // |> filter(fn: (r) => r._field == "finishedAt") // |> aggregateWindow(every: 1m fn: mean, createEmpty: false) // option task = { // name: "Name this Check", // every: 1m, // offset: 0s // } // check = { // _check_id: "undefined", // _check_name: "Name this Check", // _type: "custom", // tags: {a: "b",c: "d"} // } // warn = (r) =>(r.finishedAt> 20) // crit = (r) =>(r.finishedAt> 20) // info = (r) =>(r.finishedAt> 20) // messageFn = (r) =>("Check: ${ r._check_name } is: ${ r._level }") // data // |> v1.fieldsAsCols() // |> monitor.check(data: check, messageFn:messageFn, warn:warn, crit:crit, info:info) // GenerateFlux returns the check query text directly func (c Custom) GenerateFlux() (string, error) { return c.Query.Text, nil } // sanitizeFlux modifies the check query text to include correct _check_id param in check object func (c Custom) sanitizeFlux() (string, error) { p := parser.ParseSource(c.Query.Text) if errs := ast.GetErrors(p); len(errs) != 0 { return "", multiError(errs) } ast.Visit(p, func(n ast.Node) { if variableAssign, ok := n.(*ast.VariableAssignment); ok && variableAssign.ID.Name == "check" { if objectExp, ok := variableAssign.Init.(*ast.ObjectExpression); ok { idx := -1 for i, prop := range objectExp.Properties { if prop.Key.Key() == "_check_id" { idx = i break } } idProp := flux.Property("_check_id", flux.String(c.ID.String())) if idx >= 0 { objectExp.Properties[idx] = idProp } else { objectExp.Properties = append(objectExp.Properties, idProp) } } } }) return ast.Format(p), nil } func propertyHasValue(prop *ast.Property, key string, value string) bool { stringLit, ok := prop.Value.(*ast.StringLiteral) return ok && prop.Key.Key() == key && stringLit.Value == value } func (c *Custom) hasRequiredTaskOptions() (err error) { p := parser.ParseSource(c.Query.Text) hasOptionTask := false hasName := false nameMatchesCheck := false hasEvery := false hasOffset := false ast.Visit(p, func(n ast.Node) { if option, ok := n.(*ast.OptionStatement); ok { if variableAssign, ok := option.Assignment.(*ast.VariableAssignment); ok && variableAssign.ID.Name == "task" { hasOptionTask = true if objectExp, ok := variableAssign.Init.(*ast.ObjectExpression); ok { for _, prop := range objectExp.Properties { if prop.Key.Key() == "name" { hasName = true if propertyHasValue(prop, "name", c.Name) { nameMatchesCheck = true } } if prop.Key.Key() == "every" { hasEvery = true } if prop.Key.Key() == "offset" { hasOffset = true } } } } } }) if !hasOptionTask { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "Custom flux missing task option statement", } } if !hasName { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "Custom flux missing name parameter from task option statement", } } if hasName && !nameMatchesCheck { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "Name parameter from task option statement must match check name", } } if !hasEvery { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "Custom flux missing every parameter from task option statement", } } if !hasOffset { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "Custom flux missing offset parameter from task option statement", } } return nil } func (c *Custom) hasRequiredCheckParameters() (err error) { p := parser.ParseSource(c.Query.Text) hasCheckObject := false checkNameMatches := false checkTypeIsCustom := false ast.Visit(p, func(n ast.Node) { if variableAssign, ok := n.(*ast.VariableAssignment); ok && variableAssign.ID.Name == "check" { hasCheckObject = true if objectExp, ok := variableAssign.Init.(*ast.ObjectExpression); ok { for _, prop := range objectExp.Properties { if propertyHasValue(prop, "_check_name", c.Name) { checkNameMatches = true } if propertyHasValue(prop, "_type", "custom") { checkTypeIsCustom = true } } } } }) if !hasCheckObject { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "Custom flux must have an object called 'check'", } } if !checkNameMatches { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "_check_name parameter on check object must match check name", } } if !checkTypeIsCustom { return &influxdb.Error{ Code: influxdb.EInvalid, Msg: "_type parameter on check object must be set to 'custom'", } } return nil } // Valid checks whether check flux is valid, returns error if invalid func (c *Custom) Valid() error { if err := c.hasRequiredCheckParameters(); err != nil { return err } if err := c.hasRequiredTaskOptions(); err != nil { return err } // add or replace _check_id parameter on the check object script, err := c.sanitizeFlux() if err != nil { return err } c.Query.Text = script return nil } type customAlias Custom // MarshalJSON implement json.Marshaler interface. func (c Custom) MarshalJSON() ([]byte, error) { return json.Marshal( struct { customAlias Type string `json:"type"` }{ customAlias: customAlias(c), Type: c.Type(), }) } // Type returns the type of the check. func (c Custom) Type() string { return "custom" } // ClearPrivateData remove any data that we don't want to be exposed publicly. func (c *Custom) ClearPrivateData() { c.TaskID = 0 } // SetTaskID sets the taskID for a check. func (c *Custom) SetTaskID(id influxdb.ID) { c.TaskID = id } // GetTaskID retrieves the task ID for a check. func (c *Custom) GetTaskID() influxdb.ID { return c.TaskID } // GetOwnerID gets the ownerID associated with a Check. func (c *Custom) GetOwnerID() influxdb.ID { return c.OwnerID } // SetOwnerID sets the taskID for a check. func (c *Custom) SetOwnerID(id influxdb.ID) { c.OwnerID = id } // SetCreatedAt sets the creation time for a check func (c *Custom) SetCreatedAt(now time.Time) { c.CreatedAt = now } // SetUpdatedAt sets the update time for a check func (c *Custom) SetUpdatedAt(now time.Time) { c.UpdatedAt = now } // SetID sets the primary key for a check func (c *Custom) SetID(id influxdb.ID) { c.ID = id } // SetOrgID is SetOrgID func (c *Custom) SetOrgID(id influxdb.ID) { c.OrgID = id } // SetName implements influxdb.Updator interface func (c *Custom) SetName(name string) { c.Name = name } // SetDescription is SetDescription func (c *Custom) SetDescription(description string) { c.Description = description } // GetID is GetID func (c *Custom) GetID() influxdb.ID { return c.ID } // GetCRUDLog gets crudLog func (c *Custom) GetCRUDLog() influxdb.CRUDLog { return influxdb.CRUDLog{CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt} } // GetOrgID gets the orgID associated with the Check func (c *Custom) GetOrgID() influxdb.ID { return c.OrgID } // GetName implements influxdb.Getter interface. func (c *Custom) GetName() string { return c.Name } // GetDescription is GetDescription func (c *Custom) GetDescription() string { return c.Description }