194 lines
5.4 KiB
Go
194 lines
5.4 KiB
Go
// Copyright 2019, OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package configcheck has checks to be applied to configuration
|
|
// objects implemented by factories of components used in the OpenTelemetry
|
|
// collector. It is recommended for implementers of components to run the
|
|
// validations available on this package.
|
|
package configcheck
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"go.opentelemetry.io/collector/component/componenterror"
|
|
"go.opentelemetry.io/collector/config"
|
|
)
|
|
|
|
// The regular expression for valid config field tag.
|
|
var configFieldTagRegExp = regexp.MustCompile("^[a-z0-9][a-z0-9_]*$")
|
|
|
|
// ValidateConfigFromFactories checks if all configurations for the given factories
|
|
// are satisfying the patterns used by the collector.
|
|
func ValidateConfigFromFactories(factories config.Factories) error {
|
|
var errs []error
|
|
var configs []interface{}
|
|
|
|
for _, factory := range factories.Receivers {
|
|
configs = append(configs, factory.CreateDefaultConfig())
|
|
}
|
|
for _, factory := range factories.Processors {
|
|
configs = append(configs, factory.CreateDefaultConfig())
|
|
}
|
|
for _, factory := range factories.Exporters {
|
|
configs = append(configs, factory.CreateDefaultConfig())
|
|
}
|
|
for _, factory := range factories.Extensions {
|
|
configs = append(configs, factory.CreateDefaultConfig())
|
|
}
|
|
|
|
for _, config := range configs {
|
|
if err := ValidateConfig(config); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
|
|
return componenterror.CombineErrors(errs)
|
|
}
|
|
|
|
// ValidateConfig enforces that given configuration object is following the patterns
|
|
// used by the collector. This ensures consistency between different implementations
|
|
// of components and extensions. It is recommended for implementers of components
|
|
// to call this function on their tests passing the default configuration of the
|
|
// component factory.
|
|
func ValidateConfig(config interface{}) error {
|
|
t := reflect.TypeOf(config)
|
|
|
|
tk := t.Kind()
|
|
if t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
tk = t.Kind()
|
|
}
|
|
|
|
if tk != reflect.Struct {
|
|
return fmt.Errorf(
|
|
"config must be a struct or a pointer to one, the passed object is a %s",
|
|
tk)
|
|
}
|
|
|
|
return validateConfigDataType(t)
|
|
}
|
|
|
|
// validateConfigDataType performs a descending validation of the given type.
|
|
// If the type is a struct it goes to each of its fields to check for the proper
|
|
// tags.
|
|
func validateConfigDataType(t reflect.Type) error {
|
|
var errs []error
|
|
|
|
switch t.Kind() {
|
|
case reflect.Ptr:
|
|
if err := validateConfigDataType(t.Elem()); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
case reflect.Struct:
|
|
// Reflect on the pointed data and check each of its fields.
|
|
nf := t.NumField()
|
|
for i := 0; i < nf; i++ {
|
|
f := t.Field(i)
|
|
if err := checkStructFieldTags(f); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
default:
|
|
// The config object can carry other types but they are not used when
|
|
// reading the configuration via viper so ignore them. Basically ignore:
|
|
// reflect.Uintptr, reflect.Chan, reflect.Func, reflect.Interface, and
|
|
// reflect.UnsafePointer.
|
|
}
|
|
|
|
if err := componenterror.CombineErrors(errs); err != nil {
|
|
return fmt.Errorf(
|
|
"type %q from package %q has invalid config settings: %v",
|
|
t.Name(),
|
|
t.PkgPath(),
|
|
err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkStructFieldTags inspects the tags of a struct field.
|
|
func checkStructFieldTags(f reflect.StructField) error {
|
|
|
|
tagValue := f.Tag.Get("mapstructure")
|
|
if tagValue == "" {
|
|
|
|
// Ignore special types.
|
|
switch f.Type.Kind() {
|
|
case reflect.Interface, reflect.Chan, reflect.Func, reflect.Uintptr, reflect.UnsafePointer:
|
|
// Allow the config to carry the types above, but since they are not read
|
|
// when loading configuration, just ignore them.
|
|
return nil
|
|
}
|
|
|
|
// Public fields of other types should be tagged.
|
|
chars := []byte(f.Name)
|
|
if len(chars) > 0 && chars[0] >= 'A' && chars[0] <= 'Z' {
|
|
return fmt.Errorf("mapstructure tag not present on field %q", f.Name)
|
|
}
|
|
|
|
// Not public field, no need to have a tag.
|
|
return nil
|
|
}
|
|
|
|
tagParts := strings.Split(tagValue, ",")
|
|
if tagParts[0] != "" {
|
|
if tagParts[0] == "-" {
|
|
// Nothing to do, as mapstructure decode skips this field.
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Check if squash is specified.
|
|
squash := false
|
|
for _, tag := range tagParts[1:] {
|
|
if tag == "squash" {
|
|
squash = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if squash {
|
|
// Field was squashed.
|
|
if f.Type.Kind() != reflect.Struct {
|
|
return fmt.Errorf(
|
|
"attempt to squash non-struct type on field %q", f.Name)
|
|
}
|
|
}
|
|
|
|
switch f.Type.Kind() {
|
|
case reflect.Struct:
|
|
// It is another struct, continue down-level
|
|
return validateConfigDataType(f.Type)
|
|
|
|
case reflect.Map, reflect.Slice, reflect.Array:
|
|
// The element of map, array, or slice can be itself a configuration object.
|
|
return validateConfigDataType(f.Type.Elem())
|
|
|
|
default:
|
|
fieldTag := tagParts[0]
|
|
if !configFieldTagRegExp.MatchString(fieldTag) {
|
|
return fmt.Errorf(
|
|
"field %q has config tag %q which doesn't satisfy %q",
|
|
f.Name,
|
|
fieldTag,
|
|
configFieldTagRegExp.String())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|