mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 18:19:04 +02:00
265 lines
6.9 KiB
Go
265 lines
6.9 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// Copyright 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.
|
|
|
|
//go:build windows
|
|
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/alecthomas/kingpin/v2"
|
|
"go.yaml.in/yaml/v3"
|
|
)
|
|
|
|
// validateKeys checks that node, the value of the dotted key path, only uses
|
|
// the keys of the schema typ.
|
|
//
|
|
// The configuration file sets flags, and the flags parse and validate the
|
|
// values (see [checkFlagValue]). So the schema only defines the keys: a leaf
|
|
// takes any scalar or a list of scalars, whatever the type of its field.
|
|
// Decoding the values into the field types would reject values that the flags
|
|
// accept, like compress: "true" or a comma-separated list for a list field.
|
|
//
|
|
// A struct that decodes itself, like the performancecounter block, is decoded
|
|
// to validate its contents.
|
|
func validateKeys(node *yaml.Node, path string, typ reflect.Type) error {
|
|
switch node.Kind {
|
|
case yaml.DocumentNode:
|
|
for _, content := range node.Content {
|
|
if err := validateKeys(content, path, typ); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
case yaml.AliasNode:
|
|
return validateKeys(node.Alias, path, typ)
|
|
case yaml.ScalarNode:
|
|
if node.Tag == "!!null" {
|
|
// A key without a value leaves the flags unchanged.
|
|
return nil
|
|
}
|
|
case yaml.MappingNode, yaml.SequenceNode:
|
|
}
|
|
|
|
if reflect.PointerTo(typ).Implements(reflect.TypeFor[yaml.Unmarshaler]()) {
|
|
//nolint:wrapcheck // the decoder errors name the line and field.
|
|
return node.Decode(reflect.New(typ).Interface())
|
|
}
|
|
|
|
if typ.Kind() != reflect.Struct {
|
|
if !isValue(node) {
|
|
return fmt.Errorf("line %d: %s must be a value or a list of values", node.Line, displayPath(path))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if node.Kind != yaml.MappingNode {
|
|
return fmt.Errorf("line %d: %s must be a mapping", node.Line, displayPath(path))
|
|
}
|
|
|
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
|
key, value := node.Content[i], node.Content[i+1]
|
|
|
|
if isMerge(key) {
|
|
// A merge key (<<) adds the keys of other mappings.
|
|
for _, source := range mergeSources(value) {
|
|
if err := validateKeys(source, path, typ); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
field, ok := schemaField(typ, key.Value)
|
|
if !ok {
|
|
return fmt.Errorf("line %d: field %s not found in %s", key.Line, key.Value, displayPath(path))
|
|
}
|
|
|
|
if err := validateKeys(value, joinPath(path, key.Value), field.Type); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isValue reports whether node is the value of a flag: a scalar, or a list of
|
|
// scalars for a repeatable or list flag.
|
|
func isValue(node *yaml.Node) bool {
|
|
switch node.Kind {
|
|
case yaml.ScalarNode:
|
|
return true
|
|
case yaml.AliasNode:
|
|
return isValue(node.Alias)
|
|
case yaml.SequenceNode:
|
|
for _, item := range node.Content {
|
|
if item.Kind == yaml.AliasNode {
|
|
item = item.Alias
|
|
}
|
|
|
|
if item.Kind != yaml.ScalarNode {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
case yaml.DocumentNode, yaml.MappingNode:
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// keyLines records the line of every dotted key path below node in lines.
|
|
// Explicit keys take precedence over the keys of merged mappings, like in the
|
|
// decoded values.
|
|
func keyLines(node *yaml.Node, path string, lines map[string]int) {
|
|
switch node.Kind {
|
|
case yaml.DocumentNode:
|
|
for _, content := range node.Content {
|
|
keyLines(content, path, lines)
|
|
}
|
|
case yaml.AliasNode:
|
|
keyLines(node.Alias, path, lines)
|
|
case yaml.MappingNode:
|
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
|
if isMerge(node.Content[i]) {
|
|
for _, source := range mergeSources(node.Content[i+1]) {
|
|
keyLines(source, path, lines)
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
|
key, value := node.Content[i], node.Content[i+1]
|
|
if isMerge(key) {
|
|
continue
|
|
}
|
|
|
|
keyPath := joinPath(path, key.Value)
|
|
lines[keyPath] = key.Line
|
|
keyLines(value, keyPath, lines)
|
|
}
|
|
case yaml.ScalarNode, yaml.SequenceNode:
|
|
}
|
|
}
|
|
|
|
func isMerge(key *yaml.Node) bool {
|
|
return key.Kind == yaml.ScalarNode && key.Tag == "!!merge"
|
|
}
|
|
|
|
// mergeSources returns the mappings that a merge key merges: an alias, a
|
|
// mapping or a list of them.
|
|
func mergeSources(value *yaml.Node) []*yaml.Node {
|
|
if value.Kind == yaml.SequenceNode {
|
|
return value.Content
|
|
}
|
|
|
|
return []*yaml.Node{value}
|
|
}
|
|
|
|
func joinPath(path, key string) string {
|
|
if path == "" {
|
|
return key
|
|
}
|
|
|
|
return path + "." + key
|
|
}
|
|
|
|
func displayPath(path string) string {
|
|
if path == "" {
|
|
return "the configuration"
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
// schemaField returns the field of the struct typ with the YAML key name,
|
|
// looking into the fields inlined with the yaml inline option.
|
|
func schemaField(typ reflect.Type, name string) (reflect.StructField, bool) {
|
|
for field := range typ.Fields() {
|
|
if !field.IsExported() {
|
|
continue
|
|
}
|
|
|
|
key, options, _ := strings.Cut(field.Tag.Get("yaml"), ",")
|
|
|
|
if slices.Contains(strings.Split(options, ","), "inline") && field.Type.Kind() == reflect.Struct {
|
|
if inlined, ok := schemaField(field.Type, name); ok {
|
|
return inlined, true
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if key == "" {
|
|
key = strings.ToLower(field.Name)
|
|
}
|
|
|
|
if key != "-" && key == name {
|
|
return field, true
|
|
}
|
|
}
|
|
|
|
return reflect.StructField{}, false
|
|
}
|
|
|
|
// checkFlagValue checks that the flag accepts the values from the
|
|
// configuration file.
|
|
//
|
|
// The flags only parse their default values in kingpin.Application.Parse,
|
|
// which does not tell the configuration key, and does not parse them at all
|
|
// if the flag is also set on the command line. kingpin cannot copy a flag
|
|
// value, so the check sets the flag value itself. That is safe: Parse sets it
|
|
// again, to the same default or to the command-line value. A repeatable flag
|
|
// would append the values twice, so its list is restored after the check.
|
|
func checkFlagValue(flag *kingpin.FlagClause, values []string) error {
|
|
value := flag.Model().Value
|
|
|
|
if repeatable, ok := value.(interface{ IsCumulative() bool }); !ok || !repeatable.IsCumulative() {
|
|
//nolint:wrapcheck // the caller adds the key and line.
|
|
return value.Set(strings.Join(values, ","))
|
|
}
|
|
|
|
getter, ok := value.(kingpin.Getter)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
list := reflect.ValueOf(getter.Get())
|
|
if list.Kind() != reflect.Pointer || list.Elem().Kind() != reflect.Slice {
|
|
// The list cannot be restored; Parse checks the values.
|
|
return nil
|
|
}
|
|
|
|
saved := reflect.ValueOf(list.Elem().Interface())
|
|
defer list.Elem().Set(saved)
|
|
|
|
for _, v := range values {
|
|
if err := value.Set(v); err != nil {
|
|
//nolint:wrapcheck // the caller adds the key and line.
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|