influxdb/variable.go

252 lines
7.1 KiB
Go

package influxdb
import (
"context"
"encoding/json"
"fmt"
"net/url"
"regexp"
)
// ErrVariableNotFound is the error msg for a missing variable.
const ErrVariableNotFound = "variable not found"
// ops for variable error.
const (
OpFindVariableByID = "FindVariableByID"
OpFindVariables = "FindVariables"
OpCreateVariable = "CreateVariable"
OpUpdateVariable = "UpdateVariable"
OpReplaceVariable = "ReplaceVariable"
OpDeleteVariable = "DeleteVariable"
)
// VariableService describes a service for managing Variables
type VariableService interface {
// FindVariableByID finds a single variable from the store by its ID
FindVariableByID(ctx context.Context, id ID) (*Variable, error)
// FindVariables returns all variables in the store
FindVariables(ctx context.Context, filter VariableFilter, opt ...FindOptions) ([]*Variable, error)
// CreateVariable creates a new variable and assigns it an ID
CreateVariable(ctx context.Context, m *Variable) error
// UpdateVariable updates a single variable with a changeset
UpdateVariable(ctx context.Context, id ID, update *VariableUpdate) (*Variable, error)
// ReplaceVariable replaces a single variable
ReplaceVariable(ctx context.Context, variable *Variable) error
// DeleteVariable removes a variable from the store
DeleteVariable(ctx context.Context, id ID) error
}
// A Variable describes a keyword that can be expanded into several possible
// values when used in an InfluxQL or Flux query
type Variable struct {
ID ID `json:"id,omitempty"`
OrganizationID ID `json:"orgID,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Selected []string `json:"selected"`
Arguments *VariableArguments `json:"arguments"`
CRUDLog
}
// DefaultVariableFindOptions are the default find options for variables.
var DefaultVariableFindOptions = FindOptions{}
// VariableFilter represents a set of filter that restrict the returned results.
type VariableFilter struct {
ID *ID
OrganizationID *ID
Organization *string
}
// QueryParams implements PagingFilter.
//
// It converts VariableFilter fields to url query params.
func (f VariableFilter) QueryParams() map[string][]string {
qp := url.Values{}
if f.ID != nil {
qp.Add("id", f.ID.String())
}
if f.OrganizationID != nil {
qp.Add("orgID", f.OrganizationID.String())
}
if f.Organization != nil {
qp.Add("org", *f.Organization)
}
return qp
}
// A VariableUpdate describes a set of changes that can be applied to a Variable
type VariableUpdate struct {
Name string `json:"name"`
Selected []string `json:"selected"`
Description string `json:"description"`
Arguments *VariableArguments `json:"arguments"`
}
// A VariableArguments contains arguments used when expanding a Variable
type VariableArguments struct {
Type string `json:"type"` // "constant", "map", or "query"
Values interface{} `json:"values"` // either VariableQueryValues, VariableConstantValues, VariableMapValues
}
// VariableQueryValues contains a query used when expanding a query-based Variable
type VariableQueryValues struct {
Query string `json:"query"`
Language string `json:"language"` // "influxql" or "flux"
}
// VariableConstantValues are the data for expanding a constants-based Variable
type VariableConstantValues []string
// VariableMapValues are the data for expanding a map-based Variable
type VariableMapValues map[string]string
// Valid returns an error if a Variable contains invalid data
func (m *Variable) Valid() error {
// todo(leodido) > check it org ID validity?
if m.Name == "" {
return fmt.Errorf("missing variable name")
}
// variable name must start with a letter to be a valid identifier in Flux
if !regexp.MustCompile(`^[a-zA-Z_].*`).MatchString(m.Name) {
return fmt.Errorf("variable name must start with a letter")
}
validTypes := map[string]bool{
"constant": true,
"map": true,
"query": true,
}
if m.Arguments == nil || !validTypes[m.Arguments.Type] {
return fmt.Errorf("invalid arguments type")
}
inValidNames := [11]string{"and", "import", "not", "return", "option", "test", "empty", "in", "or", "package", "builtin"}
for x := range inValidNames {
if m.Name == inValidNames[x] {
return fmt.Errorf("%q is a protected variable name", inValidNames[x])
}
}
return nil
}
// Valid returns an error if a Variable changeset is not valid
func (u *VariableUpdate) Valid() error {
if u.Name == "" && u.Description == "" && u.Selected == nil && u.Arguments == nil {
return fmt.Errorf("no fields supplied in update")
}
return nil
}
// Apply applies non-zero fields from a VariableUpdate to a Variable
func (u *VariableUpdate) Apply(m *Variable) {
if u.Name != "" {
m.Name = u.Name
}
if u.Selected != nil {
m.Selected = u.Selected
}
if u.Arguments != nil {
m.Arguments = u.Arguments
}
if u.Description != "" {
m.Description = u.Description
}
}
// UnmarshalJSON unmarshals json into a VariableArguments struct, using the `Type`
// field to assign the approriate struct to the `Values` field
func (a *VariableArguments) UnmarshalJSON(data []byte) error {
type Alias VariableArguments
aux := struct{ *Alias }{Alias: (*Alias)(a)}
err := json.Unmarshal(data, &aux)
if err != nil {
return err
}
// Decode the polymorphic VariableArguments.Values field into the appropriate struct
switch aux.Type {
case "constant":
values, ok := aux.Values.([]interface{})
if !ok {
return fmt.Errorf("error parsing %v as VariableConstantArguments", aux.Values)
}
variableValues := make(VariableConstantValues, len(values))
for i, v := range values {
if _, ok := v.(string); !ok {
return fmt.Errorf("expected variable constant value to be string but received %T", v)
}
variableValues[i] = v.(string)
}
a.Values = variableValues
case "map":
values, ok := aux.Values.(map[string]interface{})
if !ok {
return fmt.Errorf("error parsing %v as VariableMapArguments", aux.Values)
}
variableValues := VariableMapValues{}
for k, v := range values {
if _, ok := v.(string); !ok {
return fmt.Errorf("expected variable map value to be string but received %T", v)
}
variableValues[k] = v.(string)
}
a.Values = variableValues
case "query":
values, ok := aux.Values.(map[string]interface{})
if !ok {
return fmt.Errorf("error parsing %v as VariableQueryArguments", aux.Values)
}
variableValues := VariableQueryValues{}
query, prs := values["query"]
if !prs {
return fmt.Errorf("\"query\" key not present in VariableQueryArguments")
}
if _, ok := query.(string); !ok {
return fmt.Errorf("expected \"query\" to be string but received %T", query)
}
language, prs := values["language"]
if !prs {
return fmt.Errorf("\"language\" key not present in VariableQueryArguments")
}
if _, ok := language.(string); !ok {
return fmt.Errorf("expected \"language\" to be string but received %T", language)
}
variableValues.Query = query.(string)
variableValues.Language = language.(string)
a.Values = variableValues
default:
return fmt.Errorf("unknown VariableArguments type %s", aux.Type)
}
return nil
}