blob: 1b73e624bfe762c1ef1e88ffd0ed1a713e892f72 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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.
// Copyright Istio Authors
//
// 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"
"strings"
)
import (
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/kylelemons/godebug/diff"
"sigs.k8s.io/yaml"
)
// 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 == ""
}
// 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
}
// DiffYAML accepts two single yaml(not with "---" yaml separator).
// Returns a string containing a line-by-line unified diff of the linewise
// changes required to make A into B. Each line is prefixed with '+', '-', or
// ' ' to indicate if it should be added, removed, or is correct respectively.
//
// eg:
// a: key: val
//
// key1: val1
//
// b: key: val
//
// key1: val2
//
// return: key: val
//
// -key1: val1
// +key1: val2
func DiffYAML(a, b string) (string, error) {
// Unmarshal and Marshal to keep the order of the keys consistent
aMap, bMap := make(map[string]any), make(map[string]any)
if err := yaml.Unmarshal([]byte(a), &aMap); err != nil {
return "", err
}
if err := yaml.Unmarshal([]byte(b), &bMap); err != nil {
return "", err
}
var aYaml, bYaml string
if len(aMap) != 0 {
aBytes, err := yaml.Marshal(aMap)
if err != nil {
return "", err
}
aYaml = string(aBytes)
}
if len(bMap) != 0 {
bBytes, err := yaml.Marshal(bMap)
if err != nil {
return "", err
}
bYaml = string(bBytes)
}
return diff.Diff(aYaml, bYaml), nil
}
// SplitYAML uses yaml separator that begins with "---" to split yaml file.
func SplitYAML(y string) ([]string, error) {
var buf bytes.Buffer
var segments []string
scanner := bufio.NewScanner(strings.NewReader(y))
for scanner.Scan() {
line := scanner.Text()
// yaml separator
if strings.HasPrefix(line, "---") {
segments = append(segments, buf.String())
buf.Reset()
continue
}
if _, err := buf.WriteString(line + "\n"); err != nil {
return nil, err
}
}
// add the last yaml(not empty)
if buf.String() != "" {
segments = append(segments, buf.String()+"\n")
}
return segments, nil
}
// JoinYAML use "---" to join yaml segments.
func JoinYAML(segs []string) string {
var resBuilder strings.Builder
for _, seg := range segs {
seg = strings.TrimSpace(seg)
// todo: support yaml separator begin with "---"
if !strings.HasSuffix(seg, "---") {
resBuilder.WriteString(seg + "\n---\n")
} else {
resBuilder.WriteString(seg + "\n")
}
}
return resBuilder.String()
}