1// Copyright 2019 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 15package util 16 17import ( 18 "bytes" 19 "fmt" 20 "strings" 21 22 jsonpatch "github.com/evanphx/json-patch" 23 yaml2 "github.com/ghodss/yaml" 24 "github.com/gogo/protobuf/jsonpb" 25 "github.com/gogo/protobuf/proto" 26 jsonpb2 "github.com/golang/protobuf/jsonpb" 27 "github.com/kylelemons/godebug/diff" 28 "sigs.k8s.io/yaml" 29) 30 31// ToYAML returns a YAML string representation of val, or the error string if an error occurs. 32func ToYAML(val interface{}) string { 33 y, err := yaml2.Marshal(val) 34 if err != nil { 35 return err.Error() 36 } 37 return string(y) 38} 39 40// ToYAMLWithJSONPB returns a YAML string representation of val (using jsonpb), or the error string if an error occurs. 41func ToYAMLWithJSONPB(val proto.Message) string { 42 m := jsonpb.Marshaler{EnumsAsInts: true} 43 js, err := m.MarshalToString(val) 44 if err != nil { 45 return err.Error() 46 } 47 yb, err := yaml.JSONToYAML([]byte(js)) 48 if err != nil { 49 return err.Error() 50 } 51 return string(yb) 52} 53 54// MarshalWithJSONPB returns a YAML string representation of val (using jsonpb). 55func MarshalWithJSONPB(val proto.Message) (string, error) { 56 m := jsonpb.Marshaler{EnumsAsInts: true} 57 js, err := m.MarshalToString(val) 58 if err != nil { 59 return "", err 60 } 61 yb, err := yaml.JSONToYAML([]byte(js)) 62 if err != nil { 63 return "", err 64 } 65 return string(yb), nil 66} 67 68// UnmarshalWithJSONPB unmarshals y into out using gogo jsonpb (required for many proto defined structs). 69func UnmarshalWithJSONPB(y string, out proto.Message, allowUnknownField bool) error { 70 jb, err := yaml.YAMLToJSON([]byte(y)) 71 if err != nil { 72 return err 73 } 74 75 u := jsonpb.Unmarshaler{AllowUnknownFields: allowUnknownField} 76 err = u.Unmarshal(bytes.NewReader(jb), out) 77 if err != nil { 78 return err 79 } 80 return nil 81} 82 83// UnmarshalValuesWithJSONPB unmarshals y into out using golang jsonpb. 84func UnmarshalValuesWithJSONPB(y string, out proto.Message, allowUnknown bool) error { 85 jb, err := yaml.YAMLToJSON([]byte(y)) 86 if err != nil { 87 return err 88 } 89 u := jsonpb2.Unmarshaler{AllowUnknownFields: allowUnknown} 90 err = u.Unmarshal(bytes.NewReader(jb), out) 91 if err != nil { 92 return err 93 } 94 return nil 95} 96 97/*func ObjectsInManifest(mstr string) string { 98 ao, err := manifest.ParseObjectsFromYAMLManifest(mstr) 99 if err != nil { 100 return err.Error() 101 } 102 var out []string 103 for _, v := range ao { 104 out = append(out, v.Hash()) 105 } 106 return strings.Join(out, "\n") 107}*/ 108 109// OverlayTrees performs a sequential JSON strategic of overlays over base. 110func OverlayTrees(base map[string]interface{}, overlays ...map[string]interface{}) (map[string]interface{}, error) { 111 bby, err := yaml.Marshal(base) 112 if err != nil { 113 return nil, err 114 } 115 by := string(bby) 116 117 for _, o := range overlays { 118 oy, err := yaml.Marshal(o) 119 if err != nil { 120 return nil, err 121 } 122 123 by, err = OverlayYAML(by, string(oy)) 124 if err != nil { 125 return nil, err 126 } 127 } 128 129 out := make(map[string]interface{}) 130 err = yaml.Unmarshal([]byte(by), &out) 131 if err != nil { 132 return nil, err 133 } 134 return out, nil 135} 136 137// OverlayYAML patches the overlay tree over the base tree and returns the result. All trees are expressed as YAML 138// strings. 139func OverlayYAML(base, overlay string) (string, error) { 140 if strings.TrimSpace(base) == "" { 141 return overlay, nil 142 } 143 if strings.TrimSpace(overlay) == "" { 144 return base, nil 145 } 146 bj, err := yaml2.YAMLToJSON([]byte(base)) 147 if err != nil { 148 return "", fmt.Errorf("yamlToJSON error in base: %s\n%s", err, bj) 149 } 150 oj, err := yaml2.YAMLToJSON([]byte(overlay)) 151 if err != nil { 152 return "", fmt.Errorf("yamlToJSON error in overlay: %s\n%s", err, oj) 153 } 154 if base == "" { 155 bj = []byte("{}") 156 } 157 if overlay == "" { 158 oj = []byte("{}") 159 } 160 161 merged, err := jsonpatch.MergePatch(bj, oj) 162 if err != nil { 163 return "", fmt.Errorf("json merge error (%s) for base object: \n%s\n override object: \n%s", err, bj, oj) 164 } 165 my, err := yaml2.JSONToYAML(merged) 166 if err != nil { 167 return "", fmt.Errorf("jsonToYAML error (%s) for merged object: \n%s", err, merged) 168 } 169 170 return string(my), nil 171} 172 173func YAMLDiff(a, b string) string { 174 ao, bo := make(map[string]interface{}), make(map[string]interface{}) 175 if err := yaml.Unmarshal([]byte(a), &ao); err != nil { 176 return err.Error() 177 } 178 if err := yaml.Unmarshal([]byte(b), &bo); err != nil { 179 return err.Error() 180 } 181 182 ay, err := yaml.Marshal(ao) 183 if err != nil { 184 return err.Error() 185 } 186 by, err := yaml.Marshal(bo) 187 if err != nil { 188 return err.Error() 189 } 190 191 return diff.Diff(string(ay), string(by)) 192} 193 194// IsYAMLEqual reports whether the YAML in strings a and b are equal. 195func IsYAMLEqual(a, b string) bool { 196 if strings.TrimSpace(a) == "" && strings.TrimSpace(b) == "" { 197 return true 198 } 199 ajb, err := yaml.YAMLToJSON([]byte(a)) 200 if err != nil { 201 scope.Debugf("bad YAML in isYAMLEqual:\n%s", a) 202 return false 203 } 204 bjb, err := yaml.YAMLToJSON([]byte(b)) 205 if err != nil { 206 scope.Debugf("bad YAML in isYAMLEqual:\n%s", b) 207 return false 208 } 209 210 return string(ajb) == string(bjb) 211} 212 213// IsYAMLEmpty reports whether the YAML string y is logically empty. 214func IsYAMLEmpty(y string) bool { 215 var yc []string 216 for _, l := range strings.Split(y, "\n") { 217 yt := strings.TrimSpace(l) 218 if !strings.HasPrefix(yt, "#") && !strings.HasPrefix(yt, "---") { 219 yc = append(yc, l) 220 } 221 } 222 return strings.TrimSpace(strings.Join(yc, "\n")) == "{}" 223} 224