github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/builtin/transformer/router.go (about) 1 package transformer 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/antonmedv/expr" 8 "github.com/antonmedv/expr/vm" 9 "github.com/observiq/carbon/entry" 10 "github.com/observiq/carbon/operator" 11 "github.com/observiq/carbon/operator/helper" 12 "go.uber.org/zap" 13 ) 14 15 func init() { 16 operator.Register("router", func() operator.Builder { return NewRouterOperatorConfig("") }) 17 } 18 19 func NewRouterOperatorConfig(operatorID string) *RouterOperatorConfig { 20 return &RouterOperatorConfig{ 21 BasicConfig: helper.NewBasicConfig(operatorID, "router"), 22 } 23 } 24 25 // RouterOperatorConfig is the configuration of a router operator 26 type RouterOperatorConfig struct { 27 helper.BasicConfig `yaml:",inline"` 28 Routes []*RouterOperatorRouteConfig `json:"routes" yaml:"routes"` 29 } 30 31 // RouterOperatorRouteConfig is the configuration of a route on a router operator 32 type RouterOperatorRouteConfig struct { 33 helper.LabelerConfig `yaml:",inline"` 34 Expression string `json:"expr" yaml:"expr"` 35 OutputIDs helper.OutputIDs `json:"output" yaml:"output"` 36 } 37 38 // Build will build a router operator from the supplied configuration 39 func (c RouterOperatorConfig) Build(context operator.BuildContext) (operator.Operator, error) { 40 basicOperator, err := c.BasicConfig.Build(context) 41 if err != nil { 42 return nil, err 43 } 44 45 routes := make([]*RouterOperatorRoute, 0, len(c.Routes)) 46 for _, routeConfig := range c.Routes { 47 compiled, err := expr.Compile(routeConfig.Expression, expr.AsBool(), expr.AllowUndefinedVariables()) 48 if err != nil { 49 return nil, fmt.Errorf("failed to compile expression '%s': %w", routeConfig.Expression, err) 50 } 51 52 labeler, err := routeConfig.LabelerConfig.Build() 53 if err != nil { 54 return nil, fmt.Errorf("failed to build labeler for route '%s': %w", routeConfig.Expression, err) 55 } 56 57 route := RouterOperatorRoute{ 58 Labeler: labeler, 59 Expression: compiled, 60 OutputIDs: routeConfig.OutputIDs, 61 } 62 routes = append(routes, &route) 63 } 64 65 routerOperator := &RouterOperator{ 66 BasicOperator: basicOperator, 67 routes: routes, 68 } 69 70 return routerOperator, nil 71 } 72 73 // SetNamespace will namespace the router operator and the outputs contained in its routes 74 func (c *RouterOperatorConfig) SetNamespace(namespace string, exclusions ...string) { 75 c.BasicConfig.SetNamespace(namespace, exclusions...) 76 for _, route := range c.Routes { 77 for i, outputID := range route.OutputIDs { 78 if helper.CanNamespace(outputID, exclusions) { 79 route.OutputIDs[i] = helper.AddNamespace(outputID, namespace) 80 } 81 } 82 } 83 } 84 85 // RouterOperator is an operator that routes entries based on matching expressions 86 type RouterOperator struct { 87 helper.BasicOperator 88 routes []*RouterOperatorRoute 89 } 90 91 // RouterOperatorRoute is a route on a router operator 92 type RouterOperatorRoute struct { 93 helper.Labeler 94 Expression *vm.Program 95 OutputIDs helper.OutputIDs 96 OutputOperators []operator.Operator 97 } 98 99 // CanProcess will always return true for a router operator 100 func (p *RouterOperator) CanProcess() bool { 101 return true 102 } 103 104 // Process will route incoming entries based on matching expressions 105 func (p *RouterOperator) Process(ctx context.Context, entry *entry.Entry) error { 106 env := helper.GetExprEnv(entry) 107 defer helper.PutExprEnv(env) 108 109 for _, route := range p.routes { 110 matches, err := vm.Run(route.Expression, env) 111 if err != nil { 112 p.Warnw("Running expression returned an error", zap.Error(err)) 113 continue 114 } 115 116 // we compile the expression with "AsBool", so this should be safe 117 if matches.(bool) { 118 if err := route.Label(entry); err != nil { 119 p.Errorf("Failed to label entry: %s", err) 120 return err 121 } 122 123 for _, output := range route.OutputOperators { 124 _ = output.Process(ctx, entry) 125 } 126 break 127 } 128 } 129 130 return nil 131 } 132 133 // CanOutput will always return true for a router operator 134 func (p *RouterOperator) CanOutput() bool { 135 return true 136 } 137 138 // Outputs will return all connected operators. 139 func (p *RouterOperator) Outputs() []operator.Operator { 140 outputs := make([]operator.Operator, 0, len(p.routes)) 141 for _, route := range p.routes { 142 outputs = append(outputs, route.OutputOperators...) 143 } 144 return outputs 145 } 146 147 // SetOutputs will set the outputs of the router operator. 148 func (p *RouterOperator) SetOutputs(operators []operator.Operator) error { 149 for _, route := range p.routes { 150 outputOperators, err := p.findOperators(operators, route.OutputIDs) 151 if err != nil { 152 return fmt.Errorf("failed to set outputs on route: %s", err) 153 } 154 route.OutputOperators = outputOperators 155 } 156 return nil 157 } 158 159 // findOperators will find a subset of operators from a collection. 160 func (p *RouterOperator) findOperators(operators []operator.Operator, operatorIDs []string) ([]operator.Operator, error) { 161 result := make([]operator.Operator, 0) 162 for _, operatorID := range operatorIDs { 163 operator, err := p.findOperator(operators, operatorID) 164 if err != nil { 165 return nil, err 166 } 167 result = append(result, operator) 168 } 169 return result, nil 170 } 171 172 // findOperator will find an operator from a collection. 173 func (p *RouterOperator) findOperator(operators []operator.Operator, operatorID string) (operator.Operator, error) { 174 for _, operator := range operators { 175 if operator.ID() == operatorID { 176 return operator, nil 177 } 178 } 179 return nil, fmt.Errorf("operator %s does not exist", operatorID) 180 }