github.com/Jeffail/benthos/v3@v3.65.0/internal/docs/config.go (about) 1 package docs 2 3 import ( 4 "fmt" 5 "regexp" 6 "sort" 7 "strings" 8 9 "github.com/Jeffail/benthos/v3/internal/interop/plugins" 10 "github.com/Jeffail/gabs/v2" 11 ) 12 13 const labelExpression = `^[a-z0-9_]+$` 14 15 var ( 16 labelRe = regexp.MustCompile(labelExpression) 17 18 // ErrBadLabel is returned when creating a component with a bad label. 19 ErrBadLabel = fmt.Errorf("should match the regular expression /%v/ and must not start with an underscore", labelExpression) 20 ) 21 22 // ValidateLabel attempts to validate the contents of a component label. 23 func ValidateLabel(label string) error { 24 if strings.HasPrefix(label, "_") { 25 return ErrBadLabel 26 } 27 if !labelRe.MatchString(label) { 28 return ErrBadLabel 29 } 30 return nil 31 } 32 33 var labelField = FieldString( 34 "label", "An optional label to use as an identifier for observability data such as metrics and logging.", 35 ).OmitWhen(func(field, parent interface{}) (string, bool) { 36 gObj := gabs.Wrap(parent) 37 if typeStr, exists := gObj.S("type").Data().(string); exists && typeStr == "resource" { 38 return "label field should be omitted when pointing to a resource", true 39 } 40 if resourceStr, exists := gObj.S("resource").Data().(string); exists && resourceStr != "" { 41 return "label field should be omitted when pointing to a resource", true 42 } 43 return "", false 44 }).AtVersion("3.44.0").Linter(func(ctx LintContext, line, col int, v interface{}) []Lint { 45 l, _ := v.(string) 46 if l == "" { 47 return nil 48 } 49 if err := ValidateLabel(l); err != nil { 50 return []Lint{ 51 NewLintError(line, fmt.Sprintf("Invalid label '%v': %v", l, err)), 52 } 53 } 54 prevLine, exists := ctx.LabelsToLine[l] 55 if exists { 56 return []Lint{ 57 NewLintError(line, fmt.Sprintf("Label '%v' collides with a previously defined label at line %v", l, prevLine)), 58 } 59 } 60 ctx.LabelsToLine[l] = line 61 return nil 62 }) 63 64 func reservedFieldsByType(t Type) map[string]FieldSpec { 65 m := map[string]FieldSpec{ 66 "type": FieldString("type", ""), 67 "plugin": FieldCommon("plugin", "").HasType(FieldTypeObject), 68 } 69 if t == TypeInput || t == TypeOutput { 70 m["processors"] = FieldCommon("processors", "").Array().HasType(FieldTypeProcessor).OmitWhen(func(field, _ interface{}) (string, bool) { 71 if arr, ok := field.([]interface{}); ok && len(arr) == 0 { 72 return "field processors is empty and can be removed", true 73 } 74 return "", false 75 }) 76 } 77 if _, isLabelType := map[Type]struct{}{ 78 TypeInput: {}, 79 TypeProcessor: {}, 80 TypeOutput: {}, 81 TypeCache: {}, 82 TypeRateLimit: {}, 83 }[t]; isLabelType { 84 m["label"] = labelField 85 } 86 return m 87 } 88 89 // TODO: V4 remove this as it's not needed. 90 func refreshOldPlugins() { 91 plugins.FlushNameTypes(func(nt [2]string) { 92 RegisterDocs(ComponentSpec{ 93 Name: nt[0], 94 Type: Type(nt[1]), 95 Plugin: true, 96 Status: StatusExperimental, 97 }) 98 }) 99 } 100 101 // GetInferenceCandidate checks a generic config structure for a component and 102 // returns either the inferred type name or an error if one cannot be inferred. 103 func GetInferenceCandidate(docProvider Provider, t Type, defaultType string, raw interface{}) (string, ComponentSpec, error) { 104 m, ok := raw.(map[string]interface{}) 105 if !ok { 106 return "", ComponentSpec{}, fmt.Errorf("invalid config value %T, expected object", raw) 107 } 108 109 if tStr, ok := m["type"].(string); ok { 110 spec, exists := GetDocs(docProvider, tStr, t) 111 if !exists { 112 return "", ComponentSpec{}, fmt.Errorf("%v type '%v' was not recognised", string(t), tStr) 113 } 114 return tStr, spec, nil 115 } 116 117 var keys []string 118 for k := range m { 119 keys = append(keys, k) 120 } 121 122 return getInferenceCandidateFromList(docProvider, t, defaultType, keys) 123 } 124 125 func getInferenceCandidateFromList(docProvider Provider, t Type, defaultType string, l []string) (string, ComponentSpec, error) { 126 ignore := reservedFieldsByType(t) 127 128 var candidates []string 129 var inferred string 130 var inferredSpec ComponentSpec 131 for _, k := range l { 132 if _, exists := ignore[k]; exists { 133 continue 134 } 135 candidates = append(candidates, k) 136 if spec, exists := GetDocs(docProvider, k, t); exists { 137 if len(inferred) > 0 { 138 candidates = []string{inferred, k} 139 sort.Strings(candidates) 140 return "", ComponentSpec{}, fmt.Errorf( 141 "unable to infer %v type, multiple candidates '%v' and '%v'", string(t), candidates[0], candidates[1], 142 ) 143 } 144 inferred = k 145 inferredSpec = spec 146 } 147 } 148 149 if len(candidates) == 0 && len(defaultType) > 0 { 150 // A totally empty component config results in the default. 151 // TODO: V4 Disable this 152 if spec, exists := GetDocs(docProvider, defaultType, t); exists { 153 return defaultType, spec, nil 154 } 155 } 156 157 if inferred == "" { 158 sort.Strings(candidates) 159 return "", ComponentSpec{}, fmt.Errorf("unable to infer %v type, candidates were: %v", string(t), candidates) 160 } 161 return inferred, inferredSpec, nil 162 } 163 164 // TODO: V4 Remove this. 165 func sanitiseConditionConfig(raw interface{}, removeDeprecated bool) error { 166 // This is a nasty hack until Benthos v4. 167 m, ok := raw.(map[string]interface{}) 168 if !ok { 169 return fmt.Errorf("expected object configuration type, found: %T", raw) 170 } 171 typeStr, ok := m["type"] 172 if !ok { 173 return nil 174 } 175 for k := range m { 176 if k == typeStr || k == "type" || k == "plugin" { 177 continue 178 } 179 delete(m, k) 180 } 181 return nil 182 } 183 184 // SanitiseComponentConfig reduces a raw component configuration into only the 185 // fields for the component name configured. 186 // 187 // TODO: V4 Remove this 188 func SanitiseComponentConfig(componentType Type, raw interface{}, filter FieldFilter) error { 189 if componentType == "condition" { 190 return sanitiseConditionConfig(raw, false) 191 } 192 193 name, spec, err := GetInferenceCandidate(globalProvider, componentType, "", raw) 194 if err != nil { 195 return err 196 } 197 198 m, ok := raw.(map[string]interface{}) 199 if !ok { 200 return fmt.Errorf("expected object configuration type, found: %T", raw) 201 } 202 203 if componentConfRaw, exists := m[name]; exists { 204 spec.Config.sanitise(componentConfRaw, filter) 205 } 206 207 reservedFields := reservedFieldsByType(componentType) 208 for k, v := range m { 209 if k == name { 210 continue 211 } 212 spec, exists := reservedFields[k] 213 if !exists { 214 delete(m, k) 215 } 216 if _, omit := spec.shouldOmit(v, m); omit { 217 delete(m, k) 218 } 219 } 220 221 for name, fieldSpec := range reservedFields { 222 fieldSpec.sanitise(m[name], filter) 223 } 224 return nil 225 } 226 227 // SanitiseConfig contains fields describing the desired behaviour of the config 228 // sanitiser such as removing certain fields. 229 type SanitiseConfig struct { 230 RemoveTypeField bool 231 RemoveDeprecated bool 232 ForExample bool 233 Filter FieldFilter 234 DocsProvider Provider 235 } 236 237 // GetDocs attempts to obtain documentation for a component implementation from 238 // a docs provider in the config, or if omitted uses the global provider. 239 func (c SanitiseConfig) GetDocs(name string, ctype Type) (ComponentSpec, bool) { 240 return GetDocs(c.DocsProvider, name, ctype) 241 }