diff --git a/commands_test.go b/cmd/commands_test.go similarity index 90% rename from commands_test.go rename to cmd/commands_test.go index 9a514d66..592ab03c 100644 --- a/commands_test.go +++ b/cmd/commands_test.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" @@ -12,7 +12,7 @@ import ( ) func getRootCommand() *cobra.Command { - return newCommandCLI() + return New() } func TestRootCmd(t *testing.T) { @@ -87,7 +87,7 @@ func TestRootCmd_VersionLong(t *testing.T) { func TestReadCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -117,7 +117,7 @@ value: 3 func TestReadWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -126,7 +126,7 @@ func TestReadWithKeyAndValueCmd(t *testing.T) { func TestReadArrayCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.e.1.name") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.e.1.name") if result.Error != nil { t.Error(result.Error) } @@ -135,7 +135,7 @@ func TestReadArrayCmd(t *testing.T) { func TestReadDeepSplatCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.**") if result.Error != nil { t.Error(result.Error) } @@ -153,7 +153,7 @@ b.e.[1].value: 4 func TestReadDeepSplatWithSuffixCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**.name") + result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.**.name") if result.Error != nil { t.Error(result.Error) } @@ -165,7 +165,7 @@ b.e.[1].name: sam func TestReadWithKeyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p p examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -p p ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -174,7 +174,7 @@ func TestReadWithKeyCmd(t *testing.T) { func TestReadAnchorsCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a") + result := test.RunCmd(cmd, "read ../examples/simple-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } @@ -183,7 +183,7 @@ func TestReadAnchorsCmd(t *testing.T) { func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/simple-anchor.yaml foobar.a") + result := test.RunCmd(cmd, "read -p pv ../examples/simple-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } @@ -192,7 +192,7 @@ func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) { func TestReadMergeAnchorsOriginalCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobar.a") if result.Error != nil { t.Error(result.Error) } @@ -201,7 +201,7 @@ func TestReadMergeAnchorsOriginalCmd(t *testing.T) { func TestReadMergeAnchorsOverrideCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.thing") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobar.thing") if result.Error != nil { t.Error(result.Error) } @@ -210,7 +210,7 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) { func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "r -p pv examples/merge-anchor.yaml foobar.th*") + result := test.RunCmd(cmd, "r -p pv ../examples/merge-anchor.yaml foobar.th*") if result.Error != nil { t.Error(result.Error) } @@ -223,7 +223,7 @@ foobar.thirsty: yep func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.a") if result.Error != nil { t.Error(result.Error) } @@ -232,7 +232,7 @@ func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.thing") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.thing") if result.Error != nil { t.Error(result.Error) } @@ -241,7 +241,7 @@ func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.c") + result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.c") if result.Error != nil { t.Error(result.Error) } @@ -250,7 +250,7 @@ func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { func TestReadInvalidDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -df ../examples/sample.yaml b.c") if result.Error == nil { t.Error("Expected command to fail due to invalid path") } @@ -260,7 +260,7 @@ func TestReadInvalidDocumentIndexCmd(t *testing.T) { func TestReadBadDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d1 examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -d1 ../examples/sample.yaml b.c") if result.Error == nil { t.Error("Expected command to fail due to invalid path") } @@ -270,7 +270,7 @@ func TestReadBadDocumentIndexCmd(t *testing.T) { func TestReadOrderCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/order.yaml") + result := test.RunCmd(cmd, "read ../examples/order.yaml") if result.Error != nil { t.Error(result.Error) } @@ -283,7 +283,7 @@ application: MyApp func TestReadMultiCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d 1 examples/multiple_docs.yaml another.document") + result := test.RunCmd(cmd, "read -d 1 ../examples/multiple_docs.yaml another.document") if result.Error != nil { t.Error(result.Error) } @@ -292,7 +292,7 @@ func TestReadMultiCmd(t *testing.T) { func TestReadMultiWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p vp -d 1 examples/multiple_docs.yaml another.document") + result := test.RunCmd(cmd, "read -p vp -d 1 ../examples/multiple_docs.yaml another.document") if result.Error != nil { t.Error(result.Error) } @@ -301,7 +301,7 @@ func TestReadMultiWithKeyAndValueCmd(t *testing.T) { func TestReadMultiAllCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d* examples/multiple_docs.yaml commonKey") + result := test.RunCmd(cmd, "read -d* ../examples/multiple_docs.yaml commonKey") if result.Error != nil { t.Error(result.Error) } @@ -313,7 +313,7 @@ third document`, result.Output) func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv -d* examples/multiple_docs.yaml commonKey") + result := test.RunCmd(cmd, "read -p pv -d* ../examples/multiple_docs.yaml commonKey") if result.Error != nil { t.Error(result.Error) } @@ -326,7 +326,7 @@ commonKey: third document func TestReadCmd_ArrayYaml(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [0].gather_facts") + result := test.RunCmd(cmd, "read ../examples/array.yaml [0].gather_facts") if result.Error != nil { t.Error(result.Error) } @@ -335,7 +335,7 @@ func TestReadCmd_ArrayYaml(t *testing.T) { func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml") + result := test.RunCmd(cmd, "read ../examples/array.yaml") if result.Error != nil { t.Error(result.Error) } @@ -355,7 +355,7 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { func TestReadCmd_ArrayYaml_OneElement(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [0]") + result := test.RunCmd(cmd, "read ../examples/array.yaml [0]") if result.Error != nil { t.Error(result.Error) } @@ -373,7 +373,7 @@ serial: 1 func TestReadCmd_ArrayYaml_SplatCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*]") + result := test.RunCmd(cmd, "read ../examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -393,7 +393,7 @@ gather_facts: true func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv examples/array.yaml [*]") + result := test.RunCmd(cmd, "read -p pv ../examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -415,7 +415,7 @@ func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p p examples/array.yaml [*]") + result := test.RunCmd(cmd, "read -p p ../examples/array.yaml [*]") if result.Error != nil { t.Error(result.Error) } @@ -426,7 +426,7 @@ func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) { func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*].gather_facts") + result := test.RunCmd(cmd, "read ../examples/array.yaml [*].gather_facts") if result.Error != nil { t.Error(result.Error) } @@ -437,7 +437,7 @@ true` func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts") + result := test.RunCmd(cmd, "read ../examples/array.yaml [x].gather_facts") if result.Error == nil { t.Error("Expected command to fail due to missing arg") } @@ -447,7 +447,7 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") + result := test.RunCmd(cmd, "read ../examples/array.yaml [*].roles[x]") if result.Error == nil { t.Error("Expected command to fail due to missing arg") } @@ -511,7 +511,7 @@ func TestReadCmd_ErrorBadPath(t *testing.T) { func TestReadCmd_Verbose(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read -v examples/sample.yaml b.c") + result := test.RunCmd(cmd, "read -v ../examples/sample.yaml b.c") if result.Error != nil { t.Error(result.Error) } @@ -520,7 +520,7 @@ func TestReadCmd_Verbose(t *testing.T) { // func TestReadCmd_ToJson(t *testing.T) { // cmd := getRootCommand() -// result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c") +// result := test.RunCmd(cmd, "read -j ../examples/sample.yaml b.c") // if result.Error != nil { // t.Error(result.Error) // } @@ -529,7 +529,7 @@ func TestReadCmd_Verbose(t *testing.T) { // func TestReadCmd_ToJsonLong(t *testing.T) { // cmd := getRootCommand() -// result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c") +// result := test.RunCmd(cmd, "read --tojson ../examples/sample.yaml b.c") // if result.Error != nil { // t.Error(result.Error) // } @@ -875,7 +875,7 @@ func TestWriteCmdScript(t *testing.T) { filename := test.WriteTempYamlFile(content) defer test.RemoveTempYamlFile(filename) - updateScript := `- command: update + updateScript := `- command: update path: b.c value: 7` scriptFilename := test.WriteTempYamlFile(updateScript) @@ -1324,7 +1324,7 @@ something: else` func TestMergeCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1341,7 +1341,7 @@ c: func TestMergeNoAutoCreateCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -c=false examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge -c=false ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1355,7 +1355,7 @@ c: func TestMergeOverwriteCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -c=false --overwrite examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge -c=false --overwrite ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1369,7 +1369,7 @@ c: func TestMergeAppendCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --append examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge --autocreate=false --append ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1383,7 +1383,7 @@ c: func TestMergeOverwriteAndAppendCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite examples/data1.yaml examples/data2.yaml") + result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1397,7 +1397,7 @@ c: func TestMergeArraysCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml") + result := test.RunCmd(cmd, "merge --append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1408,7 +1408,7 @@ func TestMergeArraysCmd(t *testing.T) { func TestMergeCmd_Multi(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data1.yaml") + result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1491,7 +1491,7 @@ apples: red func TestMergeCmd_Error(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml") if result.Error == nil { t.Error("Expected command to fail due to missing arg") } @@ -1501,7 +1501,7 @@ func TestMergeCmd_Error(t *testing.T) { func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml fake-unknown") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml fake-unknown") if result.Error == nil { t.Error("Expected command to fail due to unknown file") } @@ -1515,7 +1515,7 @@ func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { } func TestMergeCmd_Inplace(t *testing.T) { - filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml")) + filename := test.WriteTempYamlFile(test.ReadTempYamlFile("../examples/data1.yaml")) err := os.Chmod(filename, os.FileMode(int(0666))) if err != nil { t.Error(err) @@ -1523,7 +1523,7 @@ func TestMergeCmd_Inplace(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s examples/data2.yaml", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s ../examples/data2.yaml", filename)) if result.Error != nil { t.Error(result.Error) } @@ -1543,7 +1543,7 @@ c: func TestMergeAllowEmptyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --allow-empty examples/data1.yaml examples/empty.yaml") + result := test.RunCmd(cmd, "merge --allow-empty ../examples/data1.yaml ../examples/empty.yaml") if result.Error != nil { t.Error(result.Error) } @@ -1557,7 +1557,7 @@ c: func TestMergeDontAllowEmptyCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge examples/data1.yaml examples/empty.yaml") + result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/empty.yaml") expectedOutput := `Could not process document index 0 as there are only 0 document(s)` test.AssertResult(t, expectedOutput, result.Error.Error()) } diff --git a/cmd/constant.go b/cmd/constant.go new file mode 100644 index 00000000..8302a624 --- /dev/null +++ b/cmd/constant.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + logging "gopkg.in/op/go-logging.v1" +) + +var customTag = "" +var printMode = "v" +var writeInplace = false +var writeScript = "" +var outputToJSON = false +var overwriteFlag = false +var autoCreateFlag = true +var allowEmptyFlag = false +var appendFlag = false +var verbose = false +var version = false +var docIndex = "0" +var log = logging.MustGetLogger("yq") +var lib = yqlib.NewYqLib() +var valueParser = yqlib.NewValueParser() diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 00000000..35ac8808 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func createDeleteCmd() *cobra.Command { + var cmdDelete = &cobra.Command{ + Use: "delete [yaml_file] [path_expression]", + Aliases: []string{"d"}, + Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", + Example: ` +yq delete things.yaml 'a.b.c' +yq delete things.yaml 'a.*.c' +yq delete things.yaml 'a.(child.subchild==co*).c' +yq delete things.yaml 'a.**' +yq delete --inplace things.yaml 'a.b.c' +yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +yq d -i things.yaml 'a.b.c' + `, + Long: `Deletes the nodes matching the given path expression from the YAML file. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +`, + RunE: deleteProperty, + } + cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdDelete +} + +func deleteProperty(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("Must provide ") + } + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) + updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} + + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} diff --git a/cmd/merge.go b/cmd/merge.go new file mode 100644 index 00000000..9ea736ae --- /dev/null +++ b/cmd/merge.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" + "strings" +) + +func createMergeCmd() *cobra.Command { + var cmdMerge = &cobra.Command{ + Use: "merge [initial_yaml_file] [additional_yaml_file]...", + Aliases: []string{"m"}, + Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", + Example: ` +yq merge things.yaml other.yaml +yq merge --inplace things.yaml other.yaml +yq m -i things.yaml other.yaml +yq m --overwrite things.yaml other.yaml +yq m -i -x things.yaml other.yaml +yq m -i -a things.yaml other.yaml +yq m -i --autocreate=false things.yaml other.yaml + `, + Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. + +If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. +If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. +`, + RunE: mergeProperties, + } + cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") + cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") + cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") + cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") + cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdMerge +} + +func mergeProperties(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("Must provide at least 2 yaml files") + } + // first generate update commands from the file + var filesToMerge = args[1:] + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) + + for _, fileToMerge := range filesToMerge { + matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0) + if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) { + return errorProcessingFile + } + for _, matchingNode := range matchingNodes { + mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag) + updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}) + } + } + + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} diff --git a/cmd/new.go b/cmd/new.go new file mode 100644 index 00000000..3ca842d0 --- /dev/null +++ b/cmd/new.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v3" +) + +func createNewCmd() *cobra.Command { + var cmdNew = &cobra.Command{ + Use: "new [path] [value]", + Aliases: []string{"n"}, + Short: "yq n [--script/-s script_file] a.b.c newValue", + Example: ` +yq new 'a.b.c' cat +yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +yq n 'a.b[+]' cat +yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags +yq n --script create_script.yaml + `, + Long: `Creates a new yaml w.r.t the given path and value. +Outputs to STDOUT + +Create Scripts: +Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. +`, + RunE: newProperty, + } + cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") + cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") + return cmdNew +} + +func newProperty(cmd *cobra.Command, args []string) error { + var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") + if updateCommandsError != nil { + return updateCommandsError + } + newNode := lib.New(updateCommands[0].Path) + + for _, updateCommand := range updateCommands { + + errorUpdating := lib.Update(&newNode, updateCommand, true) + + if errorUpdating != nil { + return errorUpdating + } + } + + var encoder = yaml.NewEncoder(cmd.OutOrStdout()) + encoder.SetIndent(2) + errorEncoding := encoder.Encode(&newNode) + encoder.Close() + return errorEncoding +} diff --git a/cmd/prefix.go b/cmd/prefix.go new file mode 100644 index 00000000..ae5c28ae --- /dev/null +++ b/cmd/prefix.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v3" +) + +func createPrefixCmd() *cobra.Command { + var cmdPrefix = &cobra.Command{ + Use: "prefix [yaml_file] [path]", + Aliases: []string{"p"}, + Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", + Example: ` +yq prefix things.yaml 'a.b.c' +yq prefix --inplace things.yaml 'a.b.c' +yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +yq p -i things.yaml 'a.b.c' +yq p --doc 2 things.yaml 'a.b.d' +yq p -d2 things.yaml 'a.b.d' + `, + Long: `Prefixes w.r.t to the yaml file at the given path. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +`, + RunE: prefixProperty, + } + cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdPrefix +} + +func prefixProperty(cmd *cobra.Command, args []string) error { + + if len(args) < 2 { + return errors.New("Must provide ") + } + updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} + log.Debugf("args %v", args) + + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + var updateData = func(dataBucket *yaml.Node, currentIndex int) error { + return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) + } + return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +} diff --git a/cmd/read.go b/cmd/read.go new file mode 100644 index 00000000..a669a080 --- /dev/null +++ b/cmd/read.go @@ -0,0 +1,52 @@ +package cmd + +import ( + errors "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func createReadCmd() *cobra.Command { + var cmdRead = &cobra.Command{ + Use: "read [yaml_file] [path_expression]", + Aliases: []string{"r"}, + Short: "yq r [--doc/-d index] sample.yaml 'a.b.c'", + Example: ` +yq read things.yaml 'a.b.c' +yq r - 'a.b.c' # reads from stdin +yq r things.yaml 'a.*.c' +yq r things.yaml 'a.**.c' # deep splat +yq r things.yaml 'a.(child.subchild==co*).c' +yq r -d1 things.yaml 'a.array[0].blah' +yq r things.yaml 'a.array[*].blah' +yq r -- things.yaml '--key-starting-with-dashes.blah' + `, + Long: "Outputs the value of the given path in the yaml file to STDOUT", + RunE: readProperty, + } + cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") + return cmdRead +} + +func readProperty(cmd *cobra.Command, args []string) error { + var path = "" + + if len(args) < 1 { + return errors.New("Must provide filename") + } else if len(args) > 1 { + path = args[1] + } + + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt) + + if errorReadingStream != nil { + return errorReadingStream + } + + return printResults(matchingNodes, cmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..ca1c5264 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "github.com/spf13/cobra" + logging "gopkg.in/op/go-logging.v1" + "os" +) + +func New() *cobra.Command { + var rootCmd = &cobra.Command{ + Use: "yq", + Short: "yq is a lightweight and portable command-line YAML processor.", + Long: `yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.`, + RunE: func(cmd *cobra.Command, args []string) error { + if version { + cmd.Print(GetVersionDisplay()) + return nil + } + cmd.Println(cmd.UsageString()) + + return nil + }, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + var format = logging.MustStringFormatter( + `%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`, + ) + var backend = logging.AddModuleLevel( + logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format)) + + if verbose { + backend.SetLevel(logging.DEBUG, "") + } else { + backend.SetLevel(logging.ERROR, "") + } + + logging.SetBackend(backend) + }, + } + + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") + rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") + rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") + + rootCmd.AddCommand( + createReadCmd(), + createWriteCmd(), + createPrefixCmd(), + createDeleteCmd(), + createNewCmd(), + createMergeCmd(), + ) + rootCmd.SetOutput(os.Stdout) + + return rootCmd +} diff --git a/cmd/utils.go b/cmd/utils.go new file mode 100644 index 00000000..eb7b7531 --- /dev/null +++ b/cmd/utils.go @@ -0,0 +1,372 @@ +package cmd + +import ( + "bufio" + "fmt" + "github.com/mikefarah/yq/v3/pkg/yqlib" + errors "github.com/pkg/errors" + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v3" + "io" + "io/ioutil" + "os" + "strconv" +) + +func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) { + var matchingNodes []*yqlib.NodeContext + + var currentIndex = 0 + var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error { + for { + var dataBucket yaml.Node + errorReading := decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + return handleEOF(updateAll, docIndexInt, currentIndex) + } + var errorParsing error + matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex) + if errorParsing != nil { + return errorParsing + } + currentIndex = currentIndex + 1 + } + }) + return matchingNodes, errorReadingStream +} + +func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { + log.Debugf("done %v / %v", currentIndex, docIndexInt) + if !updateAll && currentIndex <= docIndexInt { + return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex) + } + return nil +} + +func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) { + log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) + yqlib.DebugNode(&dataBucket) + if !updateAll && currentIndex != docIndexInt { + return originalMatchingNodes, nil + } + log.Debugf("reading %v in document %v", path, currentIndex) + matchingNodes, errorParsing := lib.Get(&dataBucket, path) + if errorParsing != nil { + return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) + } + return append(originalMatchingNodes, matchingNodes...), nil +} + +func printValue(node *yaml.Node, cmd *cobra.Command) error { + if node.Kind == yaml.ScalarNode { + cmd.Print(node.Value) + return nil + } + + bufferedWriter := bufio.NewWriter(cmd.OutOrStdout()) + defer safelyFlush(bufferedWriter) + + var encoder yqlib.Encoder + if outputToJSON { + encoder = yqlib.NewJsonEncoder(bufferedWriter) + } else { + encoder = yqlib.NewYamlEncoder(bufferedWriter) + } + if err := encoder.Encode(node); err != nil { + return err + } + return nil +} + +func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error { + if len(matchingNodes) == 0 { + log.Debug("no matching results, nothing to print") + return nil + } + + for index, mappedDoc := range matchingNodes { + switch printMode { + case "p": + cmd.Print(lib.PathStackToString(mappedDoc.PathStack)) + if index < len(matchingNodes)-1 { + cmd.Print("\n") + } + case "pv", "vp": + // put it into a node and print that. + var parentNode = yaml.Node{Kind: yaml.MappingNode} + parentNode.Content = make([]*yaml.Node, 2) + parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)} + parentNode.Content[1] = mappedDoc.Node + if err := printValue(&parentNode, cmd); err != nil { + return err + } + default: + if err := printValue(mappedDoc.Node, cmd); err != nil { + return err + } + // Printing our Scalars does not print a new line at the end + // we only want to do that if there are more values (so users can easily script extraction of values in the yaml) + if index < len(matchingNodes)-1 && mappedDoc.Node.Kind == yaml.ScalarNode { + cmd.Print("\n") + } + } + } + + return nil +} + +func parseDocumentIndex() (bool, int, error) { + if docIndex == "*" { + return true, -1, nil + } + docIndexInt64, err := strconv.ParseInt(docIndex, 10, 32) + if err != nil { + return false, -1, errors.Wrapf(err, "Document index %v is not a integer or *", docIndex) + } + return false, int(docIndexInt64), nil +} + +type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error + +func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn { + return func(decoder *yaml.Decoder) error { + var dataBucket yaml.Node + var errorReading error + var errorWriting error + var errorUpdating error + var currentIndex = 0 + + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + for { + log.Debugf("Read doc %v", currentIndex) + errorReading = decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + if !updateAll && currentIndex <= docIndexInt { + return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) + } + return nil + } else if errorReading != nil { + return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading) + } + errorUpdating = updateData(&dataBucket, currentIndex) + if errorUpdating != nil { + return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex) + } + + errorWriting = encoder.Encode(&dataBucket) + + if errorWriting != nil { + return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting) + } + currentIndex = currentIndex + 1 + } + } +} + +func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { + if updateAll || currentIndex == docIndexInt { + log.Debugf("Prefixing document %v", currentIndex) + yqlib.DebugNode(dataBucket) + updateCommand.Value = dataBucket.Content[0] + dataBucket.Content = make([]*yaml.Node, 1) + + newNode := lib.New(updateCommand.Path) + dataBucket.Content[0] = &newNode + + errorUpdating := lib.Update(dataBucket, updateCommand, true) + if errorUpdating != nil { + return errorUpdating + } + } + return nil +} + +func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + var updateData = func(dataBucket *yaml.Node, currentIndex int) error { + if updateAll || currentIndex == docIndexInt { + log.Debugf("Updating doc %v", currentIndex) + for _, updateCommand := range updateCommands { + log.Debugf("Processing update to Path %v", updateCommand.Path) + errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) + if errorUpdating != nil { + return errorUpdating + } + } + } + return nil + } + return readAndUpdate(writer, inputFile, updateData) +} + +func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { + var destination io.Writer + var destinationName string + if writeInplace { + info, err := os.Stat(inputFile) + if err != nil { + return err + } + tempFile, err := ioutil.TempFile("", "temp") + if err != nil { + return err + } + destinationName = tempFile.Name() + err = os.Chmod(destinationName, info.Mode()) + if err != nil { + return err + } + destination = tempFile + defer func() { + safelyCloseFile(tempFile) + safelyRenameFile(tempFile.Name(), inputFile) + }() + } else { + destination = stdOut + destinationName = "Stdout" + } + + log.Debugf("Writing to %v from %v", destinationName, inputFile) + + bufferedWriter := bufio.NewWriter(destination) + defer safelyFlush(bufferedWriter) + + var encoder yqlib.Encoder + if outputToJSON { + encoder = yqlib.NewJsonEncoder(bufferedWriter) + } else { + encoder = yqlib.NewYamlEncoder(bufferedWriter) + } + return readStream(inputFile, mapYamlDecoder(updateData, encoder)) +} + +type updateCommandParsed struct { + Command string + Path string + Value yaml.Node +} + +func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) { + var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) + if writeScript != "" { + var parsedCommands = make([]updateCommandParsed, 0) + + err := readData(writeScript, 0, &parsedCommands) + + if err != nil && err != io.EOF { + return nil, err + } + + log.Debugf("Read write commands file '%v'", parsedCommands) + for index := range parsedCommands { + parsedCommand := parsedCommands[index] + updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} + updateCommands = append(updateCommands, updateCommand) + } + + log.Debugf("Read write commands file '%v'", updateCommands) + } else if len(args) < expectedArgs { + return nil, errors.New(badArgsMessage) + } else { + updateCommands = make([]yqlib.UpdateCommand, 1) + log.Debug("args %v", args) + log.Debug("path %v", args[expectedArgs-2]) + log.Debug("Value %v", args[expectedArgs-1]) + updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true} + } + return updateCommands, nil +} + +func safelyRenameFile(from string, to string) { + if renameError := os.Rename(from, to); renameError != nil { + log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) + log.Debug(renameError.Error()) + // can't do this rename when running in docker to a file targeted in a mounted volume, + // so gracefully degrade to copying the entire contents. + if copyError := copyFileContents(from, to); copyError != nil { + log.Errorf("Failed copying from %v to %v", from, to) + log.Error(copyError.Error()) + } else { + removeErr := os.Remove(from) + if removeErr != nil { + log.Errorf("failed removing original file: %s", from) + } + } + } +} + +// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(in) + out, err := os.Create(dst) + if err != nil { + return err + } + defer safelyCloseFile(out) + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} + +func safelyFlush(writer *bufio.Writer) { + if err := writer.Flush(); err != nil { + log.Error("Error flushing writer!") + log.Error(err.Error()) + } + +} +func safelyCloseFile(file *os.File) { + err := file.Close() + if err != nil { + log.Error("Error closing file!") + log.Error(err.Error()) + } +} + +type yamlDecoderFn func(*yaml.Decoder) error + +func readStream(filename string, yamlDecoder yamlDecoderFn) error { + if filename == "" { + return errors.New("Must provide filename") + } + + var stream io.Reader + if filename == "-" { + stream = bufio.NewReader(os.Stdin) + } else { + file, err := os.Open(filename) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(file) + stream = file + } + return yamlDecoder(yaml.NewDecoder(stream)) +} + +func readData(filename string, indexToRead int, parsedData interface{}) error { + return readStream(filename, func(decoder *yaml.Decoder) error { + for currentIndex := 0; currentIndex < indexToRead; currentIndex++ { + errorSkipping := decoder.Decode(parsedData) + if errorSkipping != nil { + return errors.Wrapf(errorSkipping, "Error processing document at index %v, %v", currentIndex, errorSkipping) + } + } + return decoder.Decode(parsedData) + }) +} diff --git a/version.go b/cmd/version.go similarity index 98% rename from version.go rename to cmd/version.go index 588f6f73..11a00675 100644 --- a/version.go +++ b/cmd/version.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" diff --git a/version_test.go b/cmd/version_test.go similarity index 98% rename from version_test.go rename to cmd/version_test.go index 9f21427f..b012e652 100644 --- a/version_test.go +++ b/cmd/version_test.go @@ -1,4 +1,4 @@ -package main +package cmd import "testing" diff --git a/cmd/write.go b/cmd/write.go new file mode 100644 index 00000000..04422f4b --- /dev/null +++ b/cmd/write.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func createWriteCmd() *cobra.Command { + var cmdWrite = &cobra.Command{ + Use: "write [yaml_file] [path_expression] [value]", + Aliases: []string{"w"}, + Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue", + Example: ` +yq write things.yaml 'a.b.c' true +yq write things.yaml 'a.*.c' true +yq write things.yaml 'a.**' true +yq write things.yaml 'a.(child.subchild==co*).c' true +yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +yq write things.yaml 'a.b.c' --tag '!!float' 3 +yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags +yq w -i things.yaml 'a.b.c' cat +yq w -i -s update_script.yaml things.yaml +yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array +yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file + `, + Long: `Updates the yaml file w.r.t the given path and value. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. + +Append value to array adds the value to the end of array. + +Update Scripts: +Note that you can give an update script to perform more sophisticated update. Update script +format is list of update commands (update or delete) like so: +--- +- command: update + path: b.c + value: + #great + things: frog # wow! +- command: delete + path: b.d +`, + RunE: writeProperty, + } + cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") + cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") + cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdWrite +} + +func writeProperty(cmd *cobra.Command, args []string) error { + var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ") + if updateCommandsError != nil { + return updateCommandsError + } + return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +} diff --git a/yq.go b/yq.go index ed3a65a9..ef17961a 100644 --- a/yq.go +++ b/yq.go @@ -1,725 +1,16 @@ package main import ( - "bufio" - "fmt" - "io" - "io/ioutil" - "os" - "strconv" - "strings" - - "github.com/mikefarah/yq/v3/pkg/yqlib" - - errors "github.com/pkg/errors" - - "github.com/spf13/cobra" + command "github.com/mikefarah/yq/v3/cmd" logging "gopkg.in/op/go-logging.v1" - yaml "gopkg.in/yaml.v3" + "os" ) -var customTag = "" -var printMode = "v" -var writeInplace = false -var writeScript = "" -var outputToJSON = false -var overwriteFlag = false -var autoCreateFlag = true -var allowEmptyFlag = false -var appendFlag = false -var verbose = false -var version = false -var docIndex = "0" -var log = logging.MustGetLogger("yq") -var lib = yqlib.NewYqLib() -var valueParser = yqlib.NewValueParser() - func main() { - cmd := newCommandCLI() + cmd := command.New() + log := logging.MustGetLogger("yq") if err := cmd.Execute(); err != nil { log.Error(err.Error()) os.Exit(1) } } - -func newCommandCLI() *cobra.Command { - var rootCmd = &cobra.Command{ - Use: "yq", - Short: "yq is a lightweight and portable command-line YAML processor.", - Long: `yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.`, - RunE: func(cmd *cobra.Command, args []string) error { - if version { - cmd.Print(GetVersionDisplay()) - return nil - } - cmd.Println(cmd.UsageString()) - - return nil - }, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - var format = logging.MustStringFormatter( - `%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`, - ) - var backend = logging.AddModuleLevel( - logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format)) - - if verbose { - backend.SetLevel(logging.DEBUG, "") - } else { - backend.SetLevel(logging.ERROR, "") - } - - logging.SetBackend(backend) - }, - } - - rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") - rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") - rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") - - rootCmd.AddCommand( - createReadCmd(), - createWriteCmd(), - createPrefixCmd(), - createDeleteCmd(), - createNewCmd(), - createMergeCmd(), - ) - rootCmd.SetOutput(os.Stdout) - - return rootCmd -} - -func createReadCmd() *cobra.Command { - var cmdRead = &cobra.Command{ - Use: "read [yaml_file] [path_expression]", - Aliases: []string{"r"}, - Short: "yq r [--doc/-d index] sample.yaml 'a.b.c'", - Example: ` -yq read things.yaml 'a.b.c' -yq r - 'a.b.c' # reads from stdin -yq r things.yaml 'a.*.c' -yq r things.yaml 'a.**.c' # deep splat -yq r things.yaml 'a.(child.subchild==co*).c' -yq r -d1 things.yaml 'a.array[0].blah' -yq r things.yaml 'a.array[*].blah' -yq r -- things.yaml '--key-starting-with-dashes.blah' - `, - Long: "Outputs the value of the given path in the yaml file to STDOUT", - RunE: readProperty, - } - cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") - return cmdRead -} - -func createWriteCmd() *cobra.Command { - var cmdWrite = &cobra.Command{ - Use: "write [yaml_file] [path_expression] [value]", - Aliases: []string{"w"}, - Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'a.b.c' newValue", - Example: ` -yq write things.yaml 'a.b.c' true -yq write things.yaml 'a.*.c' true -yq write things.yaml 'a.**' true -yq write things.yaml 'a.(child.subchild==co*).c' true -yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -yq write things.yaml 'a.b.c' --tag '!!float' 3 -yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags -yq w -i things.yaml 'a.b.c' cat -yq w --script update_script.yaml things.yaml -yq w -i -s update_script.yaml things.yaml -yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array -yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file - `, - Long: `Updates the matching nodes of path expression to the specified value -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. - -Append value to array adds the value to the end of array. - -Update Scripts: -Note that you can give an update script to perform more sophisticated update. Update script -format is list of update commands (update or delete) like so: ---- -- command: update - path: b.c - value: - #great - things: frog # wow! -- command: delete - path: b.d -`, - RunE: writeProperty, - } - cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") - cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") - cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdWrite -} - -func createPrefixCmd() *cobra.Command { - var cmdPrefix = &cobra.Command{ - Use: "prefix [yaml_file] [path]", - Aliases: []string{"p"}, - Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", - Example: ` -yq prefix things.yaml 'a.b.c' -yq prefix --inplace things.yaml 'a.b.c' -yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -yq p -i things.yaml 'a.b.c' -yq p --doc 2 things.yaml 'a.b.d' -yq p -d2 things.yaml 'a.b.d' - `, - Long: `Prefixes w.r.t to the yaml file at the given path. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: prefixProperty, - } - cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdPrefix -} - -func createDeleteCmd() *cobra.Command { - var cmdDelete = &cobra.Command{ - Use: "delete [yaml_file] [path_expression]", - Aliases: []string{"d"}, - Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", - Example: ` -yq delete things.yaml 'a.b.c' -yq delete things.yaml 'a.*.c' -yq delete things.yaml 'a.(child.subchild==co*).c' -yq delete things.yaml 'a.**' -yq delete --inplace things.yaml 'a.b.c' -yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -yq d -i things.yaml 'a.b.c' - `, - Long: `Deletes the nodes matching the given path expression from the YAML file. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: deleteProperty, - } - cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdDelete -} - -func createNewCmd() *cobra.Command { - var cmdNew = &cobra.Command{ - Use: "new [path] [value]", - Aliases: []string{"n"}, - Short: "yq n [--script/-s script_file] a.b.c newValue", - Example: ` -yq new 'a.b.c' cat -yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -yq n 'a.b[+]' cat -yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags -yq n --script create_script.yaml - `, - Long: `Creates a new yaml w.r.t the given path and value. -Outputs to STDOUT - -Create Scripts: -Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. -`, - RunE: newProperty, - } - cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") - cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") - return cmdNew -} - -func createMergeCmd() *cobra.Command { - var cmdMerge = &cobra.Command{ - Use: "merge [initial_yaml_file] [additional_yaml_file]...", - Aliases: []string{"m"}, - Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", - Example: ` -yq merge things.yaml other.yaml -yq merge --inplace things.yaml other.yaml -yq m -i things.yaml other.yaml -yq m --overwrite things.yaml other.yaml -yq m -i -x things.yaml other.yaml -yq m -i -a things.yaml other.yaml -yq m -i --autocreate=false things.yaml other.yaml - `, - Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. - -If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. -If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. -`, - RunE: mergeProperties, - } - cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") - cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") - cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") - cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") - cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdMerge -} - -func readProperty(cmd *cobra.Command, args []string) error { - var path = "" - - if len(args) < 1 { - return errors.New("Must provide filename") - } else if len(args) > 1 { - path = args[1] - } - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt) - - if errorReadingStream != nil { - return errorReadingStream - } - - return printResults(matchingNodes, cmd) -} - -func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) { - var matchingNodes []*yqlib.NodeContext - - var currentIndex = 0 - var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error { - for { - var dataBucket yaml.Node - errorReading := decoder.Decode(&dataBucket) - - if errorReading == io.EOF { - return handleEOF(updateAll, docIndexInt, currentIndex) - } - var errorParsing error - matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex) - if errorParsing != nil { - return errorParsing - } - currentIndex = currentIndex + 1 - } - }) - return matchingNodes, errorReadingStream -} - -func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { - log.Debugf("done %v / %v", currentIndex, docIndexInt) - if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex) - } - return nil -} - -func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) { - log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) - yqlib.DebugNode(&dataBucket) - if !updateAll && currentIndex != docIndexInt { - return originalMatchingNodes, nil - } - log.Debugf("reading %v in document %v", path, currentIndex) - matchingNodes, errorParsing := lib.Get(&dataBucket, path) - if errorParsing != nil { - return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) - } - return append(originalMatchingNodes, matchingNodes...), nil -} - -func printValue(node *yaml.Node, cmd *cobra.Command) error { - if node.Kind == yaml.ScalarNode { - cmd.Print(node.Value) - return nil - } - - bufferedWriter := bufio.NewWriter(cmd.OutOrStdout()) - defer safelyFlush(bufferedWriter) - - var encoder yqlib.Encoder - if outputToJSON { - encoder = yqlib.NewJsonEncoder(bufferedWriter) - } else { - encoder = yqlib.NewYamlEncoder(bufferedWriter) - } - if err := encoder.Encode(node); err != nil { - return err - } - return nil -} - -func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error { - if len(matchingNodes) == 0 { - log.Debug("no matching results, nothing to print") - return nil - } - - for index, mappedDoc := range matchingNodes { - switch printMode { - case "p": - cmd.Print(lib.PathStackToString(mappedDoc.PathStack)) - if index < len(matchingNodes)-1 { - cmd.Print("\n") - } - case "pv", "vp": - // put it into a node and print that. - var parentNode = yaml.Node{Kind: yaml.MappingNode} - parentNode.Content = make([]*yaml.Node, 2) - parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)} - parentNode.Content[1] = mappedDoc.Node - if err := printValue(&parentNode, cmd); err != nil { - return err - } - default: - if err := printValue(mappedDoc.Node, cmd); err != nil { - return err - } - // Printing our Scalars does not print a new line at the end - // we only want to do that if there are more values (so users can easily script extraction of values in the yaml) - if index < len(matchingNodes)-1 && mappedDoc.Node.Kind == yaml.ScalarNode { - cmd.Print("\n") - } - } - } - - return nil -} - -func parseDocumentIndex() (bool, int, error) { - if docIndex == "*" { - return true, -1, nil - } - docIndexInt64, err := strconv.ParseInt(docIndex, 10, 32) - if err != nil { - return false, -1, errors.Wrapf(err, "Document index %v is not a integer or *", docIndex) - } - return false, int(docIndexInt64), nil -} - -type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error - -func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn { - return func(decoder *yaml.Decoder) error { - var dataBucket yaml.Node - var errorReading error - var errorWriting error - var errorUpdating error - var currentIndex = 0 - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - for { - log.Debugf("Read doc %v", currentIndex) - errorReading = decoder.Decode(&dataBucket) - - if errorReading == io.EOF { - if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) - } - return nil - } else if errorReading != nil { - return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading) - } - errorUpdating = updateData(&dataBucket, currentIndex) - if errorUpdating != nil { - return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex) - } - - errorWriting = encoder.Encode(&dataBucket) - - if errorWriting != nil { - return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting) - } - currentIndex = currentIndex + 1 - } - } -} - -func writeProperty(cmd *cobra.Command, args []string) error { - var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ") - if updateCommandsError != nil { - return updateCommandsError - } - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} - -func mergeProperties(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide at least 2 yaml files") - } - // first generate update commands from the file - var filesToMerge = args[1:] - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) - - for _, fileToMerge := range filesToMerge { - matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0) - if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) { - return errorProcessingFile - } - for _, matchingNode := range matchingNodes { - mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag) - updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}) - } - } - - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} - -func newProperty(cmd *cobra.Command, args []string) error { - var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") - if updateCommandsError != nil { - return updateCommandsError - } - newNode := lib.New(updateCommands[0].Path) - - for _, updateCommand := range updateCommands { - - errorUpdating := lib.Update(&newNode, updateCommand, true) - - if errorUpdating != nil { - return errorUpdating - } - } - - var encoder = yaml.NewEncoder(cmd.OutOrStdout()) - encoder.SetIndent(2) - errorEncoding := encoder.Encode(&newNode) - encoder.Close() - return errorEncoding -} - -func prefixProperty(cmd *cobra.Command, args []string) error { - - if len(args) < 2 { - return errors.New("Must provide ") - } - updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} - log.Debugf("args %v", args) - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) - } - return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -} - -func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Prefixing document %v", currentIndex) - yqlib.DebugNode(dataBucket) - updateCommand.Value = dataBucket.Content[0] - dataBucket.Content = make([]*yaml.Node, 1) - - newNode := lib.New(updateCommand.Path) - dataBucket.Content[0] = &newNode - - errorUpdating := lib.Update(dataBucket, updateCommand, true) - if errorUpdating != nil { - return errorUpdating - } - } - return nil -} - -func deleteProperty(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide ") - } - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) - updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} - - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} - -func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Updating doc %v", currentIndex) - for _, updateCommand := range updateCommands { - log.Debugf("Processing update to Path %v", updateCommand.Path) - errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) - if errorUpdating != nil { - return errorUpdating - } - } - } - return nil - } - return readAndUpdate(writer, inputFile, updateData) -} - -func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { - var destination io.Writer - var destinationName string - if writeInplace { - info, err := os.Stat(inputFile) - if err != nil { - return err - } - tempFile, err := ioutil.TempFile("", "temp") - if err != nil { - return err - } - destinationName = tempFile.Name() - err = os.Chmod(destinationName, info.Mode()) - if err != nil { - return err - } - destination = tempFile - defer func() { - safelyCloseFile(tempFile) - safelyRenameFile(tempFile.Name(), inputFile) - }() - } else { - destination = stdOut - destinationName = "Stdout" - } - - log.Debugf("Writing to %v from %v", destinationName, inputFile) - - bufferedWriter := bufio.NewWriter(destination) - defer safelyFlush(bufferedWriter) - - var encoder yqlib.Encoder - if outputToJSON { - encoder = yqlib.NewJsonEncoder(bufferedWriter) - } else { - encoder = yqlib.NewYamlEncoder(bufferedWriter) - } - return readStream(inputFile, mapYamlDecoder(updateData, encoder)) -} - -type updateCommandParsed struct { - Command string - Path string - Value yaml.Node -} - -func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) { - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) - if writeScript != "" { - var parsedCommands = make([]updateCommandParsed, 0) - - err := readData(writeScript, 0, &parsedCommands) - - if err != nil && err != io.EOF { - return nil, err - } - - log.Debugf("Read write commands file '%v'", parsedCommands) - for index := range parsedCommands { - parsedCommand := parsedCommands[index] - updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} - updateCommands = append(updateCommands, updateCommand) - } - - log.Debugf("Read write commands file '%v'", updateCommands) - } else if len(args) < expectedArgs { - return nil, errors.New(badArgsMessage) - } else { - updateCommands = make([]yqlib.UpdateCommand, 1) - log.Debug("args %v", args) - log.Debug("path %v", args[expectedArgs-2]) - log.Debug("Value %v", args[expectedArgs-1]) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true} - } - return updateCommands, nil -} - -func safelyRenameFile(from string, to string) { - if renameError := os.Rename(from, to); renameError != nil { - log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) - log.Debug(renameError.Error()) - // can't do this rename when running in docker to a file targeted in a mounted volume, - // so gracefully degrade to copying the entire contents. - if copyError := copyFileContents(from, to); copyError != nil { - log.Errorf("Failed copying from %v to %v", from, to) - log.Error(copyError.Error()) - } else { - removeErr := os.Remove(from) - if removeErr != nil { - log.Errorf("failed removing original file: %s", from) - } - } - } -} - -// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -func copyFileContents(src, dst string) (err error) { - in, err := os.Open(src) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(in) - out, err := os.Create(dst) - if err != nil { - return err - } - defer safelyCloseFile(out) - if _, err = io.Copy(out, in); err != nil { - return err - } - return out.Sync() -} - -func safelyFlush(writer *bufio.Writer) { - if err := writer.Flush(); err != nil { - log.Error("Error flushing writer!") - log.Error(err.Error()) - } - -} -func safelyCloseFile(file *os.File) { - err := file.Close() - if err != nil { - log.Error("Error closing file!") - log.Error(err.Error()) - } -} - -type yamlDecoderFn func(*yaml.Decoder) error - -func readStream(filename string, yamlDecoder yamlDecoderFn) error { - if filename == "" { - return errors.New("Must provide filename") - } - - var stream io.Reader - if filename == "-" { - stream = bufio.NewReader(os.Stdin) - } else { - file, err := os.Open(filename) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(file) - stream = file - } - return yamlDecoder(yaml.NewDecoder(stream)) -} - -func readData(filename string, indexToRead int, parsedData interface{}) error { - return readStream(filename, func(decoder *yaml.Decoder) error { - for currentIndex := 0; currentIndex < indexToRead; currentIndex++ { - errorSkipping := decoder.Decode(parsedData) - if errorSkipping != nil { - return errors.Wrapf(errorSkipping, "Error processing document at index %v, %v", currentIndex, errorSkipping) - } - } - return decoder.Decode(parsedData) - }) -}