mirror of
https://github.com/alibaba/higress.git
synced 2026-05-06 03:17:25 +08:00
feat: add profile/install/uninstall/upgrade command (#538)
This commit is contained in:
318
pkg/cmd/hgctl/util/yaml.go
Normal file
318
pkg/cmd/hgctl/util/yaml.go
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5" // nolint: staticcheck
|
||||
"github.com/kylelemons/godebug/diff"
|
||||
yaml3 "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
//
|
||||
//func ToYAMLGeneric(root any) ([]byte, error) {
|
||||
// var vs []byte
|
||||
// if proto, ok := root.(proto.Message); ok {
|
||||
// v, err := protomarshal.ToYAML(proto)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// vs = []byte(v)
|
||||
// } else {
|
||||
// v, err := yaml.Marshal(root)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// vs = v
|
||||
// }
|
||||
// return vs, nil
|
||||
//}
|
||||
//
|
||||
//func MustToYAMLGeneric(root any) string {
|
||||
// var vs []byte
|
||||
// if proto, ok := root.(proto.Message); ok {
|
||||
// v, err := protomarshal.ToYAML(proto)
|
||||
// if err != nil {
|
||||
// return err.Error()
|
||||
// }
|
||||
// vs = []byte(v)
|
||||
// } else {
|
||||
// v, err := yaml.Marshal(root)
|
||||
// if err != nil {
|
||||
// return err.Error()
|
||||
// }
|
||||
// vs = v
|
||||
// }
|
||||
// return string(vs)
|
||||
//}
|
||||
|
||||
// ToYAML returns a YAML string representation of val, or the error string if an error occurs.
|
||||
func ToYAML(val any) string {
|
||||
y, err := yaml.Marshal(val)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(y)
|
||||
}
|
||||
|
||||
//
|
||||
//// ToYAMLWithJSONPB returns a YAML string representation of val (using jsonpb), or the error string if an error occurs.
|
||||
//func ToYAMLWithJSONPB(val proto.Message) string {
|
||||
// v := reflect.ValueOf(val)
|
||||
// if val == nil || (v.Kind() == reflect.Ptr && v.IsNil()) {
|
||||
// return "null"
|
||||
// }
|
||||
// js, err := protomarshal.ToJSONWithOptions(val, "", true)
|
||||
// if err != nil {
|
||||
// return err.Error()
|
||||
// }
|
||||
// yb, err := yaml.JSONToYAML([]byte(js))
|
||||
// if err != nil {
|
||||
// return err.Error()
|
||||
// }
|
||||
// return string(yb)
|
||||
//}
|
||||
//
|
||||
//// MarshalWithJSONPB returns a YAML string representation of val (using jsonpb).
|
||||
//func MarshalWithJSONPB(val proto.Message) (string, error) {
|
||||
// return protomarshal.ToYAML(val)
|
||||
//}
|
||||
//
|
||||
//// UnmarshalWithJSONPB unmarshals y into out using gogo jsonpb (required for many proto defined structs).
|
||||
//func UnmarshalWithJSONPB(y string, out proto.Message, allowUnknownField bool) error {
|
||||
// // Treat nothing as nothing. If we called jsonpb.Unmarshaler it would return the same.
|
||||
// if y == "" {
|
||||
// return nil
|
||||
// }
|
||||
// jb, err := yaml.YAMLToJSON([]byte(y))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// if allowUnknownField {
|
||||
// err = protomarshal.UnmarshalAllowUnknown(jb, out)
|
||||
// } else {
|
||||
// err = protomarshal.Unmarshal(jb, out)
|
||||
// }
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// OverlayTrees performs a sequential JSON strategic of overlays over base.
|
||||
func OverlayTrees(base map[string]any, overlays ...map[string]any) (map[string]any, error) {
|
||||
needsOverlay := false
|
||||
for _, o := range overlays {
|
||||
if len(o) > 0 {
|
||||
needsOverlay = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needsOverlay {
|
||||
// Avoid expensive overlay if possible
|
||||
return base, nil
|
||||
}
|
||||
bby, err := yaml.Marshal(base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
by := string(bby)
|
||||
|
||||
for _, o := range overlays {
|
||||
oy, err := yaml.Marshal(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
by, err = OverlayYAML(by, string(oy))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
out := make(map[string]any)
|
||||
err = yaml.Unmarshal([]byte(by), &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// OverlayYAML patches the overlay tree over the base tree and returns the result. All trees are expressed as YAML
|
||||
// strings.
|
||||
func OverlayYAML(base, overlay string) (string, error) {
|
||||
if strings.TrimSpace(base) == "" {
|
||||
return overlay, nil
|
||||
}
|
||||
if strings.TrimSpace(overlay) == "" {
|
||||
return base, nil
|
||||
}
|
||||
bj, err := yaml.YAMLToJSON([]byte(base))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("yamlToJSON error in base: %s\n%s", err, bj)
|
||||
}
|
||||
oj, err := yaml.YAMLToJSON([]byte(overlay))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("yamlToJSON error in overlay: %s\n%s", err, oj)
|
||||
}
|
||||
if base == "" {
|
||||
bj = []byte("{}")
|
||||
}
|
||||
if overlay == "" {
|
||||
oj = []byte("{}")
|
||||
}
|
||||
|
||||
merged, err := jsonpatch.MergePatch(bj, oj)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("json merge error (%s) for base object: \n%s\n override object: \n%s", err, bj, oj)
|
||||
}
|
||||
my, err := yaml.JSONToYAML(merged)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("jsonToYAML error (%s) for merged object: \n%s", err, merged)
|
||||
}
|
||||
|
||||
return string(my), nil
|
||||
}
|
||||
|
||||
// yamlDiff compares single YAML file
|
||||
func yamlDiff(a, b string) string {
|
||||
ao, bo := make(map[string]any), make(map[string]any)
|
||||
if err := yaml.Unmarshal([]byte(a), &ao); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(b), &bo); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
ay, err := yaml.Marshal(ao)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
by, err := yaml.Marshal(bo)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return diff.Diff(string(ay), string(by))
|
||||
}
|
||||
|
||||
// yamlStringsToList yaml string parse to string list
|
||||
func yamlStringsToList(str string) []string {
|
||||
reader := bufio.NewReader(strings.NewReader(str))
|
||||
decoder := yaml3.NewYAMLReader(reader)
|
||||
res := make([]string, 0)
|
||||
for {
|
||||
doc, err := decoder.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
chunk := bytes.TrimSpace(doc)
|
||||
res = append(res, string(chunk))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// multiYamlDiffOutput multi yaml diff output format
|
||||
func multiYamlDiffOutput(res, diff string) string {
|
||||
if res == "" {
|
||||
return diff
|
||||
}
|
||||
if diff == "" {
|
||||
return res
|
||||
}
|
||||
|
||||
return res + "\n" + diff
|
||||
}
|
||||
|
||||
func diffStringList(l1, l2 []string) string {
|
||||
var maxLen int
|
||||
var minLen int
|
||||
var l1Max bool
|
||||
res := ""
|
||||
if len(l1)-len(l2) > 0 {
|
||||
maxLen = len(l1)
|
||||
minLen = len(l2)
|
||||
l1Max = true
|
||||
} else {
|
||||
maxLen = len(l2)
|
||||
minLen = len(l1)
|
||||
l1Max = false
|
||||
}
|
||||
|
||||
for i := 0; i < maxLen; i++ {
|
||||
d := ""
|
||||
if i >= minLen {
|
||||
if l1Max {
|
||||
d = yamlDiff(l1[i], "")
|
||||
} else {
|
||||
d = yamlDiff("", l2[i])
|
||||
}
|
||||
} else {
|
||||
d = yamlDiff(l1[i], l2[i])
|
||||
}
|
||||
res = multiYamlDiffOutput(res, d)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// YAMLDiff compares multiple YAML files and single YAML file
|
||||
func YAMLDiff(a, b string) string {
|
||||
al := yamlStringsToList(a)
|
||||
bl := yamlStringsToList(b)
|
||||
res := diffStringList(al, bl)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// IsYAMLEqual reports whether the YAML in strings a and b are equal.
|
||||
func IsYAMLEqual(a, b string) bool {
|
||||
if strings.TrimSpace(a) == "" && strings.TrimSpace(b) == "" {
|
||||
return true
|
||||
}
|
||||
ajb, err := yaml.YAMLToJSON([]byte(a))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bjb, err := yaml.YAMLToJSON([]byte(b))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(ajb, bjb)
|
||||
}
|
||||
|
||||
// IsYAMLEmpty reports whether the YAML string y is logically empty.
|
||||
func IsYAMLEmpty(y string) bool {
|
||||
var yc []string
|
||||
for _, l := range strings.Split(y, "\n") {
|
||||
yt := strings.TrimSpace(l)
|
||||
if !strings.HasPrefix(yt, "#") && !strings.HasPrefix(yt, "---") {
|
||||
yc = append(yc, l)
|
||||
}
|
||||
}
|
||||
res := strings.TrimSpace(strings.Join(yc, "\n"))
|
||||
return res == "{}" || res == ""
|
||||
}
|
||||
Reference in New Issue
Block a user