github.com/influxdata/telegraf@v1.30.3/config/plugin_id.go (about)

     1  package config
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"sort"
     8  
     9  	"github.com/influxdata/toml/ast"
    10  )
    11  
    12  type keyValuePair struct {
    13  	Key   string
    14  	Value string
    15  }
    16  
    17  func processTable(parent string, table *ast.Table) ([]keyValuePair, error) {
    18  	var prefix string
    19  	var options []keyValuePair
    20  
    21  	if parent != "" {
    22  		prefix = parent + "."
    23  	}
    24  
    25  	for k, value := range table.Fields {
    26  		switch v := value.(type) {
    27  		case *ast.KeyValue:
    28  			key := prefix + k
    29  			options = append(options, keyValuePair{
    30  				Key:   key,
    31  				Value: v.Value.Source(),
    32  			})
    33  		case *ast.Table:
    34  			key := prefix + k
    35  			childs, err := processTable(key, v)
    36  			if err != nil {
    37  				return nil, fmt.Errorf("parsing table for %q failed: %w", key, err)
    38  			}
    39  			options = append(options, childs...)
    40  		case []*ast.Table:
    41  			for i, t := range v {
    42  				key := fmt.Sprintf("%s#%d.%s", prefix, i, k)
    43  				childs, err := processTable(key, t)
    44  				if err != nil {
    45  					return nil, fmt.Errorf("parsing table for %q #%d failed: %w", key, i, err)
    46  				}
    47  				options = append(options, childs...)
    48  			}
    49  		default:
    50  			return nil, fmt.Errorf("unknown node type %T in key %q", value, prefix+k)
    51  		}
    52  	}
    53  	return options, nil
    54  }
    55  
    56  func generatePluginID(prefix string, table *ast.Table) (string, error) {
    57  	// We need to ensure that identically configured plugins _always_
    58  	// result in the same ID no matter which order the options are specified.
    59  	// This is even more relevant as Golang does _not_ give any guarantee
    60  	// on the ordering of maps.
    61  	// So we flatten out the configuration options (also for nested objects)
    62  	// and then sort the resulting array by the canonical key-name.
    63  	cfg, err := processTable("", table)
    64  	if err != nil {
    65  		return "", fmt.Errorf("processing AST failed: %w", err)
    66  	}
    67  	sort.SliceStable(cfg, func(i, j int) bool { return cfg[i].Key < cfg[j].Key })
    68  
    69  	// Hash the config options to get the ID. We also prefix the ID with
    70  	// the plugin name to prevent overlap with other plugin types.
    71  	hash := sha256.New()
    72  	hash.Write(append([]byte(prefix), 0))
    73  	for _, kv := range cfg {
    74  		hash.Write([]byte(kv.Key + ":" + kv.Value))
    75  		hash.Write([]byte{0})
    76  	}
    77  
    78  	return hex.EncodeToString(hash.Sum(nil)), nil
    79  }