Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/util/yaml.go
4093 views
1
package util
2
3
import (
4
"errors"
5
"regexp"
6
"sort"
7
8
"gopkg.in/yaml.v2"
9
)
10
11
// RawYAML is similar to json.RawMessage and allows for deferred YAML decoding.
12
type RawYAML []byte
13
14
// UnmarshalYAML implements yaml.Unmarshaler.
15
func (r *RawYAML) UnmarshalYAML(unmarshal func(interface{}) error) error {
16
var ms yaml.MapSlice
17
if err := unmarshal(&ms); err != nil {
18
return err
19
}
20
bb, err := yaml.Marshal(ms)
21
if err != nil {
22
return err
23
}
24
*r = bb
25
return nil
26
}
27
28
// MarshalYAML implements yaml.Marshaler.
29
func (r RawYAML) MarshalYAML() (interface{}, error) {
30
return r.Map()
31
}
32
33
// Map converts the raw YAML into a yaml.MapSlice.
34
func (r RawYAML) Map() (yaml.MapSlice, error) {
35
var ms yaml.MapSlice
36
if err := yaml.Unmarshal(r, &ms); err != nil {
37
return nil, err
38
}
39
return ms, nil
40
}
41
42
// MarshalYAMLMerged marshals all values from vv into a single object.
43
func MarshalYAMLMerged(vv ...interface{}) ([]byte, error) {
44
var full yaml.MapSlice
45
for _, v := range vv {
46
bb, err := yaml.Marshal(v)
47
if err != nil {
48
return nil, err
49
}
50
ms, err := RawYAML(bb).Map()
51
if err != nil {
52
return nil, err
53
}
54
full = append(full, ms...)
55
}
56
return yaml.Marshal(full)
57
}
58
59
// UnmarshalYAMLMerged performs a strict unmarshal of bb into all values from
60
// vv.
61
func UnmarshalYAMLMerged(bb []byte, vv ...interface{}) error {
62
var typeErrors []yaml.TypeError
63
64
for _, v := range vv {
65
// Perform a strict unmarshal. This is likely to fail with type errors for
66
// missing fields that may have been consumed by another object in vv.
67
var te *yaml.TypeError
68
if err := yaml.UnmarshalStrict(bb, v); errors.As(err, &te) {
69
typeErrors = append(typeErrors, *te)
70
} else if err != nil {
71
return err
72
}
73
74
// It's common for custom yaml.Unmarshaler implementations to use
75
// UnmarshalYAML to apply default values both before and after calling the
76
// unmarshal method passed to them.
77
//
78
// We *must* do a second non-strict unmarshal *after* the strict unmarshal
79
// to ensure that every v was able to complete its unmarshal to completion,
80
// ignoring type errors from unrecognized fields.
81
if err := yaml.Unmarshal(bb, v); err != nil {
82
return err
83
}
84
}
85
86
var (
87
addedErrors = map[string]struct{}{}
88
notFoundErrors = map[string]int{}
89
)
90
91
// Do an initial pass over our errors, separating errors for not found fields.
92
// Other errors are "real" and should be returned.
93
for _, te := range typeErrors {
94
for _, msg := range te.Errors {
95
notFound := notFoundErrRegex.FindStringSubmatch(msg)
96
if notFound != nil {
97
// Track the invalid field error. Use the first capture group which
98
// excludes the type, which will be unique per v.
99
notFoundErrors[notFound[1]]++
100
continue
101
}
102
addedErrors[msg] = struct{}{}
103
}
104
}
105
106
// Iterate over our errors for not found fields. The field truly isn't defined
107
// if it was reported len(vv) times (i.e., no v consumed it).
108
for msg, count := range notFoundErrors {
109
if count == len(vv) {
110
addedErrors[msg] = struct{}{}
111
}
112
}
113
114
if len(addedErrors) > 0 {
115
realErrors := make([]string, 0, len(addedErrors))
116
for msg := range addedErrors {
117
realErrors = append(realErrors, msg)
118
}
119
sort.Strings(realErrors)
120
return &yaml.TypeError{Errors: realErrors}
121
}
122
return nil
123
}
124
125
var notFoundErrRegex = regexp.MustCompile(`^(line \d+: field .* not found) in type .*$`)
126
127