summaryrefslogtreecommitdiffstats
path: root/pkg/utils
diff options
context:
space:
mode:
authorStefan Haller <stefan@haller-berlin.de>2023-06-08 12:30:36 +0200
committerStefan Haller <stefan@haller-berlin.de>2023-06-26 08:11:10 +0200
commit85f293af1affd8ef879216a4e6bb1be3eaa8c040 (patch)
treec45d8840f9d28aa92228f24149448e231f56ce88 /pkg/utils
parent9cbd7fe69edfd8854735da1cd443420f80173cff (diff)
Add new function RenameYamlKey
Diffstat (limited to 'pkg/utils')
-rw-r--r--pkg/utils/yaml_utils/yaml_utils.go55
-rw-r--r--pkg/utils/yaml_utils/yaml_utils_test.go77
2 files changed, 132 insertions, 0 deletions
diff --git a/pkg/utils/yaml_utils/yaml_utils.go b/pkg/utils/yaml_utils/yaml_utils.go
index f38a47264..4363dedf3 100644
--- a/pkg/utils/yaml_utils/yaml_utils.go
+++ b/pkg/utils/yaml_utils/yaml_utils.go
@@ -95,3 +95,58 @@ func lookupKey(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) {
return nil, nil
}
+
+// takes a yaml document in bytes, a path to a key, and a new name for the key.
+// Will rename the key to the new name if it exists, and do nothing otherwise.
+func RenameYamlKey(yamlBytes []byte, path []string, newKey string) ([]byte, error) {
+ // Parse the YAML file.
+ var node yaml.Node
+ err := yaml.Unmarshal(yamlBytes, &node)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse YAML: %w", err)
+ }
+
+ // Empty document: nothing to do.
+ if len(node.Content) == 0 {
+ return yamlBytes, nil
+ }
+
+ body := node.Content[0]
+
+ if err := renameYamlKey(body, path, newKey); err != nil {
+ return yamlBytes, err
+ }
+
+ // Convert the updated YAML node back to YAML bytes.
+ updatedYAMLBytes, err := yaml.Marshal(body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to convert YAML node to bytes: %w", err)
+ }
+
+ return updatedYAMLBytes, nil
+}
+
+// Recursive function to rename the YAML key.
+func renameYamlKey(node *yaml.Node, path []string, newKey string) error {
+ if node.Kind != yaml.MappingNode {
+ return errors.New("yaml node in path is not a dictionary")
+ }
+
+ keyNode, valueNode := lookupKey(node, path[0])
+ if keyNode == nil {
+ return nil
+ }
+
+ // end of path reached: rename key
+ if len(path) == 1 {
+ // Check that new key doesn't exist yet
+ if newKeyNode, _ := lookupKey(node, newKey); newKeyNode != nil {
+ return fmt.Errorf("new key `%s' already exists", newKey)
+ }
+
+ keyNode.Value = newKey
+ return nil
+ }
+
+ return renameYamlKey(valueNode, path[1:], newKey)
+}
diff --git a/pkg/utils/yaml_utils/yaml_utils_test.go b/pkg/utils/yaml_utils/yaml_utils_test.go
index 6a544cee5..2e29fce75 100644
--- a/pkg/utils/yaml_utils/yaml_utils_test.go
+++ b/pkg/utils/yaml_utils/yaml_utils_test.go
@@ -106,3 +106,80 @@ func TestUpdateYamlValue(t *testing.T) {
})
}
}
+
+func TestRenameYamlKey(t *testing.T) {
+ tests := []struct {
+ name string
+ in string
+ path []string
+ newKey string
+ expectedOut string
+ expectedErr string
+ }{
+ {
+ name: "rename key",
+ in: "foo: 5\n",
+ path: []string{"foo"},
+ newKey: "bar",
+ expectedOut: "bar: 5\n",
+ expectedErr: "",
+ },
+ {
+ name: "rename key, nested",
+ in: "foo:\n bar: 5\n",
+ path: []string{"foo", "bar"},
+ newKey: "baz",
+ // indentation is not preserved. See https://github.com/go-yaml/yaml/issues/899
+ expectedOut: "foo:\n baz: 5\n",
+ expectedErr: "",
+ },
+ {
+ name: "rename non-scalar key",
+ in: "foo:\n bar: 5\n",
+ path: []string{"foo"},
+ newKey: "qux",
+ // indentation is not preserved. See https://github.com/go-yaml/yaml/issues/899
+ expectedOut: "qux:\n bar: 5\n",
+ expectedErr: "",
+ },
+
+ // Error cases
+ {
+ name: "existing document is not a dictionary",
+ in: "42\n",
+ path: []string{"foo"},
+ newKey: "bar",
+ expectedOut: "42\n",
+ expectedErr: "yaml node in path is not a dictionary",
+ },
+ {
+ name: "not all path elements are dictionaries",
+ in: "foo:\n bar: [1, 2, 3]\n",
+ path: []string{"foo", "bar", "baz"},
+ newKey: "qux",
+ expectedOut: "foo:\n bar: [1, 2, 3]\n",
+ expectedErr: "yaml node in path is not a dictionary",
+ },
+ {
+ name: "new key exists",
+ in: "foo: 5\nbar: 7\n",
+ path: []string{"foo"},
+ newKey: "bar",
+ expectedOut: "foo: 5\nbar: 7\n",
+ expectedErr: "new key `bar' already exists",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ out, actualErr := RenameYamlKey([]byte(test.in), test.path, test.newKey)
+ if test.expectedErr == "" {
+ assert.NoError(t, actualErr)
+ } else {
+ assert.EqualError(t, actualErr, test.expectedErr)
+ }
+
+ assert.Equal(t, test.expectedOut, string(out))
+ })
+ }
+}