istio

Форк
0
322 строки · 7.2 Кб
1
// Copyright Istio Authors
2
//
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
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
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.
14

15
package util
16

17
import (
18
	"bufio"
19
	"bytes"
20
	"fmt"
21
	"io"
22
	"reflect"
23
	"strings"
24

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"
29
	"sigs.k8s.io/yaml"
30

31
	"istio.io/istio/pkg/util/protomarshal"
32
)
33

34
func ToYAMLGeneric(root any) ([]byte, error) {
35
	var vs []byte
36
	if proto, ok := root.(proto.Message); ok {
37
		v, err := protomarshal.ToYAML(proto)
38
		if err != nil {
39
			return nil, err
40
		}
41
		vs = []byte(v)
42
	} else {
43
		v, err := yaml.Marshal(root)
44
		if err != nil {
45
			return nil, err
46
		}
47
		vs = v
48
	}
49
	return vs, nil
50
}
51

52
func MustToYAMLGeneric(root any) string {
53
	var vs []byte
54
	if proto, ok := root.(proto.Message); ok {
55
		v, err := protomarshal.ToYAML(proto)
56
		if err != nil {
57
			return err.Error()
58
		}
59
		vs = []byte(v)
60
	} else {
61
		v, err := yaml.Marshal(root)
62
		if err != nil {
63
			return err.Error()
64
		}
65
		vs = v
66
	}
67
	return string(vs)
68
}
69

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)
73
	if err != nil {
74
		return err.Error()
75
	}
76
	return string(y)
77
}
78

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()) {
83
		return "null"
84
	}
85
	js, err := protomarshal.ToJSONWithOptions(val, "", true)
86
	if err != nil {
87
		return err.Error()
88
	}
89
	yb, err := yaml.JSONToYAML([]byte(js))
90
	if err != nil {
91
		return err.Error()
92
	}
93
	return string(yb)
94
}
95

96
// MarshalWithJSONPB returns a YAML string representation of val (using jsonpb).
97
func MarshalWithJSONPB(val proto.Message) (string, error) {
98
	return protomarshal.ToYAML(val)
99
}
100

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.
104
	if y == "" {
105
		return nil
106
	}
107
	jb, err := yaml.YAMLToJSON([]byte(y))
108
	if err != nil {
109
		return err
110
	}
111

112
	if allowUnknownField {
113
		err = protomarshal.UnmarshalAllowUnknown(jb, out)
114
	} else {
115
		err = protomarshal.Unmarshal(jb, out)
116
	}
117
	if err != nil {
118
		return err
119
	}
120
	return nil
121
}
122

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 {
127
		if len(o) > 0 {
128
			needsOverlay = true
129
			break
130
		}
131
	}
132
	if !needsOverlay {
133
		// Avoid expensive overlay if possible
134
		return base, nil
135
	}
136
	bby, err := yaml.Marshal(base)
137
	if err != nil {
138
		return nil, err
139
	}
140
	by := string(bby)
141

142
	for _, o := range overlays {
143
		oy, err := yaml.Marshal(o)
144
		if err != nil {
145
			return nil, err
146
		}
147

148
		by, err = OverlayYAML(by, string(oy))
149
		if err != nil {
150
			return nil, err
151
		}
152
	}
153

154
	out := make(map[string]any)
155
	err = yaml.Unmarshal([]byte(by), &out)
156
	if err != nil {
157
		return nil, err
158
	}
159
	return out, nil
160
}
161

162
// OverlayYAML patches the overlay tree over the base tree and returns the result. All trees are expressed as YAML
163
// strings.
164
func OverlayYAML(base, overlay string) (string, error) {
165
	if strings.TrimSpace(base) == "" {
166
		return overlay, nil
167
	}
168
	if strings.TrimSpace(overlay) == "" {
169
		return base, nil
170
	}
171
	bj, err := yaml.YAMLToJSON([]byte(base))
172
	if err != nil {
173
		return "", fmt.Errorf("yamlToJSON error in base: %s\n%s", err, bj)
174
	}
175
	oj, err := yaml.YAMLToJSON([]byte(overlay))
176
	if err != nil {
177
		return "", fmt.Errorf("yamlToJSON error in overlay: %s\n%s", err, oj)
178
	}
179
	if base == "" {
180
		bj = []byte("{}")
181
	}
182
	if overlay == "" {
183
		oj = []byte("{}")
184
	}
185

186
	merged, err := jsonpatch.MergePatch(bj, oj)
187
	if err != nil {
188
		return "", fmt.Errorf("json merge error (%s) for base object: \n%s\n override object: \n%s", err, bj, oj)
189
	}
190
	my, err := yaml.JSONToYAML(merged)
191
	if err != nil {
192
		return "", fmt.Errorf("jsonToYAML error (%s) for merged object: \n%s", err, merged)
193
	}
194

195
	return string(my), nil
196
}
197

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 {
202
		return err.Error()
203
	}
204
	if err := yaml.Unmarshal([]byte(b), &bo); err != nil {
205
		return err.Error()
206
	}
207

208
	ay, err := yaml.Marshal(ao)
209
	if err != nil {
210
		return err.Error()
211
	}
212
	by, err := yaml.Marshal(bo)
213
	if err != nil {
214
		return err.Error()
215
	}
216

217
	return diff.Diff(string(ay), string(by))
218
}
219

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)
225
	for {
226
		doc, err := decoder.Read()
227
		if err == io.EOF {
228
			break
229
		}
230
		if err != nil {
231
			break
232
		}
233

234
		chunk := bytes.TrimSpace(doc)
235
		res = append(res, string(chunk))
236
	}
237
	return res
238
}
239

240
// multiYamlDiffOutput multi yaml diff output format
241
func multiYamlDiffOutput(res, diff string) string {
242
	if res == "" {
243
		return diff
244
	}
245
	if diff == "" {
246
		return res
247
	}
248

249
	return res + "\n" + diff
250
}
251

252
func diffStringList(l1, l2 []string) string {
253
	var maxLen int
254
	var minLen int
255
	var l1Max bool
256
	res := ""
257
	if len(l1)-len(l2) > 0 {
258
		maxLen = len(l1)
259
		minLen = len(l2)
260
		l1Max = true
261
	} else {
262
		maxLen = len(l2)
263
		minLen = len(l1)
264
		l1Max = false
265
	}
266

267
	for i := 0; i < maxLen; i++ {
268
		d := ""
269
		if i >= minLen {
270
			if l1Max {
271
				d = yamlDiff(l1[i], "")
272
			} else {
273
				d = yamlDiff("", l2[i])
274
			}
275
		} else {
276
			d = yamlDiff(l1[i], l2[i])
277
		}
278
		res = multiYamlDiffOutput(res, d)
279
	}
280
	return res
281
}
282

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)
288

289
	return res
290
}
291

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) == "" {
295
		return true
296
	}
297
	ajb, err := yaml.YAMLToJSON([]byte(a))
298
	if err != nil {
299
		scope.Debugf("bad YAML in isYAMLEqual:\n%s", a)
300
		return false
301
	}
302
	bjb, err := yaml.YAMLToJSON([]byte(b))
303
	if err != nil {
304
		scope.Debugf("bad YAML in isYAMLEqual:\n%s", b)
305
		return false
306
	}
307

308
	return bytes.Equal(ajb, bjb)
309
}
310

311
// IsYAMLEmpty reports whether the YAML string y is logically empty.
312
func IsYAMLEmpty(y string) bool {
313
	var yc []string
314
	for _, l := range strings.Split(y, "\n") {
315
		yt := strings.TrimSpace(l)
316
		if !strings.HasPrefix(yt, "#") && !strings.HasPrefix(yt, "---") {
317
			yc = append(yc, l)
318
		}
319
	}
320
	res := strings.TrimSpace(strings.Join(yc, "\n"))
321
	return res == "{}" || res == ""
322
}
323

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.