1
// Copyright Istio Authors
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
25
jsonpatch "github.com/evanphx/json-patch/v5" // nolint: staticcheck
26
"github.com/kylelemons/godebug/diff"
27
"google.golang.org/protobuf/proto"
28
yaml3 "k8s.io/apimachinery/pkg/util/yaml"
31
"istio.io/istio/pkg/util/protomarshal"
34
func ToYAMLGeneric(root any) ([]byte, error) {
36
if proto, ok := root.(proto.Message); ok {
37
v, err := protomarshal.ToYAML(proto)
43
v, err := yaml.Marshal(root)
52
func MustToYAMLGeneric(root any) string {
54
if proto, ok := root.(proto.Message); ok {
55
v, err := protomarshal.ToYAML(proto)
61
v, err := yaml.Marshal(root)
70
// ToYAML returns a YAML string representation of val, or the error string if an error occurs.
71
func ToYAML(val any) string {
72
y, err := yaml.Marshal(val)
79
// ToYAMLWithJSONPB returns a YAML string representation of val (using jsonpb), or the error string if an error occurs.
80
func ToYAMLWithJSONPB(val proto.Message) string {
81
v := reflect.ValueOf(val)
82
if val == nil || (v.Kind() == reflect.Ptr && v.IsNil()) {
85
js, err := protomarshal.ToJSONWithOptions(val, "", true)
89
yb, err := yaml.JSONToYAML([]byte(js))
96
// MarshalWithJSONPB returns a YAML string representation of val (using jsonpb).
97
func MarshalWithJSONPB(val proto.Message) (string, error) {
98
return protomarshal.ToYAML(val)
101
// UnmarshalWithJSONPB unmarshals y into out using gogo jsonpb (required for many proto defined structs).
102
func UnmarshalWithJSONPB(y string, out proto.Message, allowUnknownField bool) error {
103
// Treat nothing as nothing. If we called jsonpb.Unmarshaler it would return the same.
107
jb, err := yaml.YAMLToJSON([]byte(y))
112
if allowUnknownField {
113
err = protomarshal.UnmarshalAllowUnknown(jb, out)
115
err = protomarshal.Unmarshal(jb, out)
123
// OverlayTrees performs a sequential JSON strategic of overlays over base.
124
func OverlayTrees(base map[string]any, overlays ...map[string]any) (map[string]any, error) {
125
needsOverlay := false
126
for _, o := range overlays {
133
// Avoid expensive overlay if possible
136
bby, err := yaml.Marshal(base)
142
for _, o := range overlays {
143
oy, err := yaml.Marshal(o)
148
by, err = OverlayYAML(by, string(oy))
154
out := make(map[string]any)
155
err = yaml.Unmarshal([]byte(by), &out)
162
// OverlayYAML patches the overlay tree over the base tree and returns the result. All trees are expressed as YAML
164
func OverlayYAML(base, overlay string) (string, error) {
165
if strings.TrimSpace(base) == "" {
168
if strings.TrimSpace(overlay) == "" {
171
bj, err := yaml.YAMLToJSON([]byte(base))
173
return "", fmt.Errorf("yamlToJSON error in base: %s\n%s", err, bj)
175
oj, err := yaml.YAMLToJSON([]byte(overlay))
177
return "", fmt.Errorf("yamlToJSON error in overlay: %s\n%s", err, oj)
186
merged, err := jsonpatch.MergePatch(bj, oj)
188
return "", fmt.Errorf("json merge error (%s) for base object: \n%s\n override object: \n%s", err, bj, oj)
190
my, err := yaml.JSONToYAML(merged)
192
return "", fmt.Errorf("jsonToYAML error (%s) for merged object: \n%s", err, merged)
195
return string(my), nil
198
// yamlDiff compares single YAML file
199
func yamlDiff(a, b string) string {
200
ao, bo := make(map[string]any), make(map[string]any)
201
if err := yaml.Unmarshal([]byte(a), &ao); err != nil {
204
if err := yaml.Unmarshal([]byte(b), &bo); err != nil {
208
ay, err := yaml.Marshal(ao)
212
by, err := yaml.Marshal(bo)
217
return diff.Diff(string(ay), string(by))
220
// yamlStringsToList yaml string parse to string list
221
func yamlStringsToList(str string) []string {
222
reader := bufio.NewReader(strings.NewReader(str))
223
decoder := yaml3.NewYAMLReader(reader)
224
res := make([]string, 0)
226
doc, err := decoder.Read()
234
chunk := bytes.TrimSpace(doc)
235
res = append(res, string(chunk))
240
// multiYamlDiffOutput multi yaml diff output format
241
func multiYamlDiffOutput(res, diff string) string {
249
return res + "\n" + diff
252
func diffStringList(l1, l2 []string) string {
257
if len(l1)-len(l2) > 0 {
267
for i := 0; i < maxLen; i++ {
271
d = yamlDiff(l1[i], "")
273
d = yamlDiff("", l2[i])
276
d = yamlDiff(l1[i], l2[i])
278
res = multiYamlDiffOutput(res, d)
283
// YAMLDiff compares multiple YAML files and single YAML file
284
func YAMLDiff(a, b string) string {
285
al := yamlStringsToList(a)
286
bl := yamlStringsToList(b)
287
res := diffStringList(al, bl)
292
// IsYAMLEqual reports whether the YAML in strings a and b are equal.
293
func IsYAMLEqual(a, b string) bool {
294
if strings.TrimSpace(a) == "" && strings.TrimSpace(b) == "" {
297
ajb, err := yaml.YAMLToJSON([]byte(a))
299
scope.Debugf("bad YAML in isYAMLEqual:\n%s", a)
302
bjb, err := yaml.YAMLToJSON([]byte(b))
304
scope.Debugf("bad YAML in isYAMLEqual:\n%s", b)
308
return bytes.Equal(ajb, bjb)
311
// IsYAMLEmpty reports whether the YAML string y is logically empty.
312
func IsYAMLEmpty(y string) bool {
314
for _, l := range strings.Split(y, "\n") {
315
yt := strings.TrimSpace(l)
316
if !strings.HasPrefix(yt, "#") && !strings.HasPrefix(yt, "---") {
320
res := strings.TrimSpace(strings.Join(yc, "\n"))
321
return res == "{}" || res == ""