github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/pipeline/config.go (about) 1 package pipeline 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/observiq/carbon/errors" 8 "github.com/observiq/carbon/operator" 9 "github.com/observiq/carbon/operator/helper" 10 yaml "gopkg.in/yaml.v2" 11 ) 12 13 // Config is the configuration of a pipeline. 14 type Config []Params 15 16 // BuildPipeline will build a pipeline from the config. 17 func (c Config) BuildPipeline(context operator.BuildContext) (*Pipeline, error) { 18 operatorConfigs, err := c.buildOperatorConfigs(context.PluginRegistry) 19 if err != nil { 20 return nil, err 21 } 22 23 operators, err := c.buildOperators(operatorConfigs, context) 24 if err != nil { 25 return nil, err 26 } 27 28 pipeline, err := NewPipeline(operators) 29 if err != nil { 30 return nil, err 31 } 32 33 return pipeline, nil 34 } 35 36 func (c Config) buildOperatorConfigs(pluginRegistry operator.PluginRegistry) ([]operator.Config, error) { 37 operatorConfigs := make([]operator.Config, 0, len(c)) 38 39 for i, params := range c { 40 if err := params.Validate(); err != nil { 41 return nil, errors.Wrap(err, "validate config params") 42 } 43 44 configs, err := params.BuildConfigs(pluginRegistry, "$", c.defaultOutput(i, "$")) 45 if err != nil { 46 return nil, errors.Wrap(err, "build operator configs") 47 } 48 operatorConfigs = append(operatorConfigs, configs...) 49 } 50 51 return operatorConfigs, nil 52 } 53 54 func (c Config) buildOperators(operatorConfigs []operator.Config, context operator.BuildContext) ([]operator.Operator, error) { 55 operators := make([]operator.Operator, 0, len(operatorConfigs)) 56 for _, operatorConfig := range operatorConfigs { 57 operator, err := operatorConfig.Build(context) 58 59 if err != nil { 60 return nil, errors.WithDetails(err, 61 "operator_id", operatorConfig.ID(), 62 "operator_type", operatorConfig.Type(), 63 ) 64 } 65 66 operators = append(operators, operator) 67 } 68 69 return operators, nil 70 } 71 72 // default returns an array containing the next operator in the pipeline 73 // if it exists, where i is the index of the current operator 74 func (c Config) defaultOutput(i int, namespace string) []string { 75 if i+1 < len(c) { 76 return []string{helper.AddNamespace(c[i+1].ID(), namespace)} 77 } 78 return []string{} 79 } 80 81 // Params is a raw params map that can be converted into an operator config. 82 type Params map[string]interface{} 83 84 // ID returns the id field in the params map. 85 func (p Params) ID() string { 86 if p.getString("id") == "" { 87 return p.getString("type") 88 } 89 return p.getString("id") 90 } 91 92 // Type returns the type field in the params map. 93 func (p Params) Type() string { 94 return p.getString("type") 95 } 96 97 // Outputs returns the output field in the params map. 98 func (p Params) Outputs() []string { 99 return p.getStringArray("output") 100 } 101 102 // NamespacedID will return the id field with a namespace. 103 func (p Params) NamespacedID(namespace string) string { 104 return helper.AddNamespace(p.ID(), namespace) 105 } 106 107 // NamespacedOutputs will return the output field with a namespace. 108 func (p Params) NamespacedOutputs(namespace string) []string { 109 outputs := p.Outputs() 110 for i, output := range outputs { 111 outputs[i] = helper.AddNamespace(output, namespace) 112 } 113 return outputs 114 } 115 116 // TemplateInput will return the template input. 117 func (p Params) TemplateInput(namespace string) string { 118 return helper.AddNamespace(p.ID(), namespace) 119 } 120 121 // TemplateOutput will return the template output. 122 func (p Params) TemplateOutput(namespace string, defaultOutput []string) string { 123 outputs := p.NamespacedOutputs(namespace) 124 if len(outputs) == 0 { 125 outputs = defaultOutput 126 } 127 return fmt.Sprintf("[%s]", strings.Join(outputs[:], ", ")) 128 } 129 130 // NamespaceExclusions will return all ids to exclude from namespacing. 131 func (p Params) NamespaceExclusions(namespace string) []string { 132 exclusions := []string{p.NamespacedID(namespace)} 133 for _, output := range p.NamespacedOutputs(namespace) { 134 exclusions = append(exclusions, output) 135 } 136 return exclusions 137 } 138 139 // Validate will validate the basic fields required to make an operator config. 140 func (p Params) Validate() error { 141 if p.Type() == "" { 142 return errors.NewError( 143 "missing required `type` field for operator config", 144 "ensure that all operator configs have a defined type field", 145 "id", p.ID(), 146 ) 147 } 148 149 return nil 150 } 151 152 // getString returns a string value from the params block. 153 func (p Params) getString(key string) string { 154 rawValue, ok := p[key] 155 if !ok { 156 return "" 157 } 158 159 stringValue, ok := rawValue.(string) 160 if !ok { 161 return "" 162 } 163 164 return stringValue 165 } 166 167 // getStringArray returns a string array from the params block. 168 func (p Params) getStringArray(key string) []string { 169 rawValue, ok := p[key] 170 if !ok { 171 return []string{} 172 } 173 174 switch value := rawValue.(type) { 175 case string: 176 return []string{value} 177 case []string: 178 return value 179 case []interface{}: 180 result := []string{} 181 for _, x := range value { 182 if strValue, ok := x.(string); ok { 183 result = append(result, strValue) 184 } 185 } 186 return result 187 default: 188 return []string{} 189 } 190 } 191 192 // BuildConfigs will build operator configs from a params map. 193 func (p Params) BuildConfigs(pluginRegistry operator.PluginRegistry, namespace string, defaultOutput []string) ([]operator.Config, error) { 194 if operator.IsDefined(p.Type()) { 195 return p.buildAsBuiltin(namespace) 196 } 197 198 if pluginRegistry.IsDefined(p.Type()) { 199 return p.buildPlugin(pluginRegistry, namespace, defaultOutput) 200 } 201 202 return nil, errors.NewError( 203 "unsupported `type` for operator config", 204 "ensure that all operators have a supported builtin or plugin type", 205 "type", p.Type(), 206 "id", p.ID(), 207 ) 208 } 209 210 // buildAsBuiltin will build a builtin config from a params map. 211 func (p Params) buildAsBuiltin(namespace string) ([]operator.Config, error) { 212 bytes, err := yaml.Marshal(p) 213 if err != nil { 214 return nil, errors.NewError( 215 "failed to parse config map as yaml", 216 "ensure that all config values are supported yaml values", 217 "error", err.Error(), 218 ) 219 } 220 221 var config operator.Config 222 if err := yaml.UnmarshalStrict(bytes, &config); err != nil { 223 return nil, err 224 } 225 226 config.SetNamespace(namespace) 227 return []operator.Config{config}, nil 228 } 229 230 // buildPlugin will build a plugin config from a params map. 231 func (p Params) buildPlugin(pluginRegistry operator.PluginRegistry, namespace string, defaultOutput []string) ([]operator.Config, error) { 232 templateParams := map[string]interface{}{} 233 for key, value := range p { 234 templateParams[key] = value 235 } 236 237 templateParams["input"] = p.TemplateInput(namespace) 238 templateParams["output"] = p.TemplateOutput(namespace, defaultOutput) 239 240 config, err := pluginRegistry.Render(p.Type(), templateParams) 241 if err != nil { 242 return nil, errors.Wrap(err, "failed to render plugin config") 243 } 244 245 exclusions := p.NamespaceExclusions(namespace) 246 for _, operatorConfig := range config.Pipeline { 247 innerNamespace := p.NamespacedID(namespace) 248 operatorConfig.SetNamespace(innerNamespace, append(exclusions, defaultOutput...)...) 249 } 250 251 return config.Pipeline, nil 252 } 253 254 // UnmarshalYAML will unmarshal yaml bytes into Params 255 func (p *Params) UnmarshalYAML(unmarshal func(interface{}) error) error { 256 var m map[interface{}]interface{} 257 err := unmarshal(&m) 258 if err != nil { 259 return err 260 } 261 262 *p = Params(cleanMap(m)) 263 return nil 264 } 265 266 func cleanMap(m map[interface{}]interface{}) map[string]interface{} { 267 clean := make(map[string]interface{}, len(m)) 268 for k, v := range m { 269 clean[fmt.Sprintf("%v", k)] = cleanValue(v) 270 } 271 return clean 272 } 273 274 func cleanValue(v interface{}) interface{} { 275 switch v := v.(type) { 276 case string, bool, int, int64, int32, float32, float64, nil: 277 return v 278 case map[interface{}]interface{}: 279 return cleanMap(v) 280 case []interface{}: 281 res := make([]interface{}, 0, len(v)) 282 for _, arrayVal := range v { 283 res = append(res, cleanValue(arrayVal)) 284 } 285 return res 286 default: 287 return fmt.Sprintf("%v", v) 288 } 289 }