| 
									
										
										
										
											2020-08-20 20:48:26 +08:00
										 |  |  | // Copyright 2020 The Prometheus 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 discovery | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"reflect" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"gopkg.in/yaml.v2" | 
					
						
							| 
									
										
										
										
											2020-10-22 17:00:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/prometheus/prometheus/discovery/targetgroup" | 
					
						
							| 
									
										
										
										
											2020-08-20 20:48:26 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	configFieldPrefix      = "AUTO_DISCOVERY_" | 
					
						
							|  |  |  | 	staticConfigsKey       = "static_configs" | 
					
						
							|  |  |  | 	staticConfigsFieldName = configFieldPrefix + staticConfigsKey | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	configNames      = make(map[string]Config) | 
					
						
							|  |  |  | 	configFieldNames = make(map[reflect.Type]string) | 
					
						
							|  |  |  | 	configFields     []reflect.StructField | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	configTypesMu sync.Mutex | 
					
						
							|  |  |  | 	configTypes   = make(map[reflect.Type]reflect.Type) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	emptyStructType = reflect.TypeOf(struct{}{}) | 
					
						
							|  |  |  | 	configsType     = reflect.TypeOf(Configs{}) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RegisterConfig registers the given Config type for YAML marshaling and unmarshaling.
 | 
					
						
							|  |  |  | func RegisterConfig(config Config) { | 
					
						
							|  |  |  | 	registerConfig(config.Name()+"_sd_configs", reflect.TypeOf(config), config) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							|  |  |  | 	// N.B.: static_configs is the only Config type implemented by default.
 | 
					
						
							|  |  |  | 	// All other types are registered at init by their implementing packages.
 | 
					
						
							|  |  |  | 	elemTyp := reflect.TypeOf(&targetgroup.Group{}) | 
					
						
							|  |  |  | 	registerConfig(staticConfigsKey, elemTyp, StaticConfig{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func registerConfig(yamlKey string, elemType reflect.Type, config Config) { | 
					
						
							|  |  |  | 	name := config.Name() | 
					
						
							|  |  |  | 	if _, ok := configNames[name]; ok { | 
					
						
							|  |  |  | 		panic(fmt.Sprintf("discovery: Config named %q is already registered", name)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	configNames[name] = config | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fieldName := configFieldPrefix + yamlKey // Field must be exported.
 | 
					
						
							|  |  |  | 	configFieldNames[elemType] = fieldName | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Insert fields in sorted order.
 | 
					
						
							|  |  |  | 	i := sort.Search(len(configFields), func(k int) bool { | 
					
						
							|  |  |  | 		return fieldName < configFields[k].Name | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	configFields = append(configFields, reflect.StructField{}) // Add empty field at end.
 | 
					
						
							|  |  |  | 	copy(configFields[i+1:], configFields[i:])                 // Shift fields to the right.
 | 
					
						
							|  |  |  | 	configFields[i] = reflect.StructField{                     // Write new field in place.
 | 
					
						
							|  |  |  | 		Name: fieldName, | 
					
						
							|  |  |  | 		Type: reflect.SliceOf(elemType), | 
					
						
							|  |  |  | 		Tag:  reflect.StructTag(`yaml:"` + yamlKey + `,omitempty"`), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func getConfigType(out reflect.Type) reflect.Type { | 
					
						
							|  |  |  | 	configTypesMu.Lock() | 
					
						
							|  |  |  | 	defer configTypesMu.Unlock() | 
					
						
							|  |  |  | 	if typ, ok := configTypes[out]; ok { | 
					
						
							|  |  |  | 		return typ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Initial exported fields map one-to-one.
 | 
					
						
							|  |  |  | 	var fields []reflect.StructField | 
					
						
							|  |  |  | 	for i, n := 0, out.NumField(); i < n; i++ { | 
					
						
							|  |  |  | 		switch field := out.Field(i); { | 
					
						
							|  |  |  | 		case field.PkgPath == "" && field.Type != configsType: | 
					
						
							|  |  |  | 			fields = append(fields, field) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			fields = append(fields, reflect.StructField{ | 
					
						
							|  |  |  | 				Name:    "_" + field.Name, // Field must be unexported.
 | 
					
						
							|  |  |  | 				PkgPath: out.PkgPath(), | 
					
						
							|  |  |  | 				Type:    emptyStructType, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Append extra config fields on the end.
 | 
					
						
							|  |  |  | 	fields = append(fields, configFields...) | 
					
						
							|  |  |  | 	typ := reflect.StructOf(fields) | 
					
						
							|  |  |  | 	configTypes[out] = typ | 
					
						
							|  |  |  | 	return typ | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UnmarshalYAMLWithInlineConfigs helps implement yaml.Unmarshal for structs
 | 
					
						
							|  |  |  | // that have a Configs field that should be inlined.
 | 
					
						
							|  |  |  | func UnmarshalYAMLWithInlineConfigs(out interface{}, unmarshal func(interface{}) error) error { | 
					
						
							|  |  |  | 	outVal := reflect.ValueOf(out) | 
					
						
							|  |  |  | 	if outVal.Kind() != reflect.Ptr { | 
					
						
							|  |  |  | 		return fmt.Errorf("discovery: can only unmarshal into a struct pointer: %T", out) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	outVal = outVal.Elem() | 
					
						
							|  |  |  | 	if outVal.Kind() != reflect.Struct { | 
					
						
							|  |  |  | 		return fmt.Errorf("discovery: can only unmarshal into a struct pointer: %T", out) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	outTyp := outVal.Type() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cfgTyp := getConfigType(outTyp) | 
					
						
							|  |  |  | 	cfgPtr := reflect.New(cfgTyp) | 
					
						
							|  |  |  | 	cfgVal := cfgPtr.Elem() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Copy shared fields (defaults) to dynamic value.
 | 
					
						
							|  |  |  | 	var configs *Configs | 
					
						
							|  |  |  | 	for i, n := 0, outVal.NumField(); i < n; i++ { | 
					
						
							|  |  |  | 		if outTyp.Field(i).Type == configsType { | 
					
						
							|  |  |  | 			configs = outVal.Field(i).Addr().Interface().(*Configs) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if cfgTyp.Field(i).PkgPath != "" { | 
					
						
							|  |  |  | 			continue // Field is unexported: ignore.
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		cfgVal.Field(i).Set(outVal.Field(i)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if configs == nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("discovery: Configs field not found in type: %T", out) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Unmarshal into dynamic value.
 | 
					
						
							|  |  |  | 	if err := unmarshal(cfgPtr.Interface()); err != nil { | 
					
						
							|  |  |  | 		return replaceYAMLTypeError(err, cfgTyp, outTyp) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Copy shared fields from dynamic value.
 | 
					
						
							|  |  |  | 	for i, n := 0, outVal.NumField(); i < n; i++ { | 
					
						
							|  |  |  | 		if cfgTyp.Field(i).PkgPath != "" { | 
					
						
							|  |  |  | 			continue // Field is unexported: ignore.
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		outVal.Field(i).Set(cfgVal.Field(i)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	*configs, err = readConfigs(cfgVal, outVal.NumField()) | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func readConfigs(structVal reflect.Value, startField int) (Configs, error) { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		configs Configs | 
					
						
							|  |  |  | 		targets []*targetgroup.Group | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	for i, n := startField, structVal.NumField(); i < n; i++ { | 
					
						
							|  |  |  | 		field := structVal.Field(i) | 
					
						
							|  |  |  | 		if field.Kind() != reflect.Slice { | 
					
						
							|  |  |  | 			panic("discovery: internal error: field is not a slice") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for k := 0; k < field.Len(); k++ { | 
					
						
							|  |  |  | 			val := field.Index(k) | 
					
						
							|  |  |  | 			if val.IsZero() || (val.Kind() == reflect.Ptr && val.Elem().IsZero()) { | 
					
						
							|  |  |  | 				key := configFieldNames[field.Type().Elem()] | 
					
						
							|  |  |  | 				key = strings.TrimPrefix(key, configFieldPrefix) | 
					
						
							|  |  |  | 				return nil, fmt.Errorf("empty or null section in %s", key) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			switch c := val.Interface().(type) { | 
					
						
							|  |  |  | 			case *targetgroup.Group: | 
					
						
							|  |  |  | 				// Add index to the static config target groups for unique identification
 | 
					
						
							|  |  |  | 				// within scrape pool.
 | 
					
						
							|  |  |  | 				c.Source = strconv.Itoa(len(targets)) | 
					
						
							|  |  |  | 				// Coalesce multiple static configs into a single static config.
 | 
					
						
							|  |  |  | 				targets = append(targets, c) | 
					
						
							|  |  |  | 			case Config: | 
					
						
							|  |  |  | 				configs = append(configs, c) | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				panic("discovery: internal error: slice element is not a Config") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(targets) > 0 { | 
					
						
							|  |  |  | 		configs = append(configs, StaticConfig(targets)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return configs, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MarshalYAMLWithInlineConfigs helps implement yaml.Marshal for structs
 | 
					
						
							|  |  |  | // that have a Configs field that should be inlined.
 | 
					
						
							|  |  |  | func MarshalYAMLWithInlineConfigs(in interface{}) (interface{}, error) { | 
					
						
							|  |  |  | 	inVal := reflect.ValueOf(in) | 
					
						
							|  |  |  | 	for inVal.Kind() == reflect.Ptr { | 
					
						
							|  |  |  | 		inVal = inVal.Elem() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	inTyp := inVal.Type() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cfgTyp := getConfigType(inTyp) | 
					
						
							|  |  |  | 	cfgPtr := reflect.New(cfgTyp) | 
					
						
							|  |  |  | 	cfgVal := cfgPtr.Elem() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Copy shared fields to dynamic value.
 | 
					
						
							|  |  |  | 	var configs *Configs | 
					
						
							|  |  |  | 	for i, n := 0, inTyp.NumField(); i < n; i++ { | 
					
						
							|  |  |  | 		if inTyp.Field(i).Type == configsType { | 
					
						
							|  |  |  | 			configs = inVal.Field(i).Addr().Interface().(*Configs) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if cfgTyp.Field(i).PkgPath != "" { | 
					
						
							|  |  |  | 			continue // Field is unexported: ignore.
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		cfgVal.Field(i).Set(inVal.Field(i)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if configs == nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("discovery: Configs field not found in type: %T", in) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := writeConfigs(cfgVal, *configs); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return cfgPtr.Interface(), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func writeConfigs(structVal reflect.Value, configs Configs) error { | 
					
						
							|  |  |  | 	targets := structVal.FieldByName(staticConfigsFieldName).Addr().Interface().(*[]*targetgroup.Group) | 
					
						
							|  |  |  | 	for _, c := range configs { | 
					
						
							|  |  |  | 		if sc, ok := c.(StaticConfig); ok { | 
					
						
							|  |  |  | 			*targets = append(*targets, sc...) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		fieldName, ok := configFieldNames[reflect.TypeOf(c)] | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return fmt.Errorf("discovery: cannot marshal unregistered Config type: %T", c) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		field := structVal.FieldByName(fieldName) | 
					
						
							|  |  |  | 		field.Set(reflect.Append(field, reflect.ValueOf(c))) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error { | 
					
						
							|  |  |  | 	if e, ok := err.(*yaml.TypeError); ok { | 
					
						
							|  |  |  | 		oldStr := oldTyp.String() | 
					
						
							|  |  |  | 		newStr := newTyp.String() | 
					
						
							|  |  |  | 		for i, s := range e.Errors { | 
					
						
							|  |  |  | 			e.Errors[i] = strings.Replace(s, oldStr, newStr, -1) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } |