yq/yq.go

691 lines
22 KiB
Go
Raw Normal View History

2015-09-26 22:15:49 +00:00
package main
import (
2018-06-12 05:33:59 +00:00
"bufio"
2015-09-28 02:00:38 +00:00
"fmt"
2018-06-12 05:33:59 +00:00
"io"
2015-09-28 02:00:38 +00:00
"io/ioutil"
"os"
"strconv"
2020-01-06 03:22:24 +00:00
"strings"
2019-12-01 20:10:42 +00:00
2019-12-06 04:57:46 +00:00
"github.com/mikefarah/yq/v3/pkg/yqlib"
2018-06-15 06:11:13 +00:00
errors "github.com/pkg/errors"
2019-09-03 05:52:55 +00:00
"github.com/spf13/cobra"
2019-10-30 21:18:09 +00:00
logging "gopkg.in/op/go-logging.v1"
2019-12-06 04:57:46 +00:00
yaml "gopkg.in/yaml.v3"
2015-09-26 22:15:49 +00:00
)
2019-12-06 05:36:42 +00:00
var customTag = ""
2019-12-22 22:08:00 +00:00
var printMode = "v"
2015-10-06 05:07:09 +00:00
var writeInplace = false
var writeScript = ""
var overwriteFlag = false
2020-01-05 04:14:14 +00:00
var autoCreateFlag = true
var allowEmptyFlag = false
2018-07-07 05:26:56 +00:00
var appendFlag = false
2017-04-11 23:16:54 +00:00
var verbose = false
var version = false
2018-06-20 01:45:51 +00:00
var docIndex = "0"
2017-12-17 22:11:08 +00:00
var log = logging.MustGetLogger("yq")
2020-01-08 21:17:56 +00:00
var lib = yqlib.NewYqLib()
var valueParser = yqlib.NewValueParser()
2015-10-06 05:07:09 +00:00
2015-09-26 22:15:49 +00:00
func main() {
cmd := newCommandCLI()
if err := cmd.Execute(); err != nil {
log.Error(err.Error())
os.Exit(1)
}
}
2017-04-11 23:16:54 +00:00
func newCommandCLI() *cobra.Command {
var rootCmd = &cobra.Command{
2019-05-14 17:28:06 +00:00
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)
},
}
2015-10-13 10:42:36 +00:00
2017-04-11 23:16:54 +00:00
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.AddCommand(
createReadCmd(),
createWriteCmd(),
2019-12-16 05:17:01 +00:00
createPrefixCmd(),
2019-12-12 09:47:22 +00:00
createDeleteCmd(),
2019-12-15 08:34:05 +00:00
createNewCmd(),
2019-12-16 10:09:23 +00:00
createMergeCmd(),
)
rootCmd.SetOutput(os.Stdout)
return rootCmd
2015-10-13 10:42:36 +00:00
}
func createReadCmd() *cobra.Command {
2018-06-12 05:33:59 +00:00
var cmdRead = &cobra.Command{
2015-10-06 05:07:09 +00:00
Use: "read [yaml_file] [path]",
Aliases: []string{"r"},
Short: "yq r [--doc/-d index] sample.yaml a.b.c",
2015-10-06 05:07:09 +00:00
Example: `
2017-12-17 22:11:08 +00:00
yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c
2018-06-12 23:13:58 +00:00
yq r -d1 things.yaml a.array[0].blah
2017-12-17 22:11:08 +00:00
yq r things.yaml a.array[*].blah
yq r -- things.yaml --key-starting-with-dashes
2015-10-13 10:42:36 +00:00
`,
2015-10-06 05:07:09 +00:00
Long: "Outputs the value of the given path in the yaml file to STDOUT",
RunE: readProperty,
2015-09-28 02:00:38 +00:00
}
2018-07-08 11:47:01 +00:00
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
2019-12-22 22:08:00 +00:00
cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), k (keys), kv (key and value pairs)")
2018-06-12 05:33:59 +00:00
return cmdRead
2015-10-13 10:42:36 +00:00
}
2015-09-26 23:38:44 +00:00
2015-10-13 10:42:36 +00:00
func createWriteCmd() *cobra.Command {
2015-10-06 05:07:09 +00:00
var cmdWrite = &cobra.Command{
Use: "write [yaml_file] [path] [value]",
Aliases: []string{"w"},
Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue",
2015-10-06 05:07:09 +00:00
Example: `
2017-12-17 22:11:08 +00:00
yq write things.yaml a.b.c cat
yq write --inplace -- things.yaml a.b.c --cat
2017-12-17 22:11:08 +00:00
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 --doc 2 things.yaml a.b.d[+] foo
yq w -d2 things.yaml a.b.d[+] foo
2015-10-13 10:42:36 +00:00
`,
2015-10-06 05:07:09 +00:00
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 updated. Update script
format is a yaml map where the key is the path and the value is..well the value. e.g.:
---
a.b.c: true,
a.b.e:
- name: bob
`,
RunE: writeProperty,
2015-10-06 05:07:09 +00:00
}
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
2019-12-06 05:36:42 +00:00
cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
2018-06-20 01:45:51 +00:00
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
2015-10-13 10:42:36 +00:00
return cmdWrite
2015-10-06 05:07:09 +00:00
}
2015-09-29 06:29:32 +00:00
2019-12-16 05:17:01 +00:00
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
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
}
2019-12-06 04:57:46 +00:00
2019-12-12 09:47:22 +00:00
func createDeleteCmd() *cobra.Command {
var cmdDelete = &cobra.Command{
Use: "delete [yaml_file] [path]",
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 --inplace things.yaml a.b.c
yq delete --inplace -- things.yaml --key-starting-with-dash
yq d -i things.yaml a.b.c
yq d things.yaml a.b.c
`,
Long: `Deletes the given path 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
}
2019-12-06 04:57:46 +00:00
2019-12-15 08:34:05 +00:00
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 cat
yq n -- --key-starting-with-dash cat
yq n --script create_script.yaml
`,
Long: `Creates a new yaml w.r.t the given path and value.
Outputs to STDOUT
2019-12-06 04:57:46 +00:00
2019-12-15 08:34:05 +00:00
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 updating yaml")
2020-01-08 21:17:56 +00:00
cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
2019-12-15 08:34:05 +00:00
return cmdNew
}
2019-12-06 04:57:46 +00:00
2019-12-16 10:09:23 +00:00
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
`,
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")
2020-01-05 04:14:14 +00:00
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")
2020-01-05 04:28:24 +00:00
cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
2020-01-06 03:22:24 +00:00
cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
2019-12-16 10:09:23 +00:00
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 {
2017-08-03 07:30:07 +00:00
var path = ""
if len(args) < 1 {
2018-07-08 11:47:01 +00:00
return errors.New("Must provide filename")
} else if len(args) > 1 {
2017-08-03 07:30:07 +00:00
path = args[1]
}
2018-07-08 11:47:01 +00:00
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
2018-06-20 01:45:51 +00:00
}
2019-12-06 04:57:46 +00:00
2020-01-05 04:14:14 +00:00
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) {
2019-12-28 07:19:37 +00:00
var matchingNodes []*yqlib.NodeContext
2019-12-09 02:44:53 +00:00
2018-07-08 11:47:01 +00:00
var currentIndex = 0
2020-01-05 04:14:14 +00:00
var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error {
2018-07-08 11:47:01 +00:00
for {
2019-12-09 02:44:53 +00:00
var dataBucket yaml.Node
2018-07-08 11:47:01 +00:00
errorReading := decoder.Decode(&dataBucket)
2019-12-16 05:46:20 +00:00
2018-07-08 11:47:01 +00:00
if errorReading == io.EOF {
2019-12-16 05:46:20 +00:00
return handleEOF(updateAll, docIndexInt, currentIndex)
2018-07-08 11:47:01 +00:00
}
2019-12-16 05:46:20 +00:00
var errorParsing error
2019-12-22 06:13:11 +00:00
matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex)
2019-12-16 05:46:20 +00:00
if errorParsing != nil {
return errorParsing
2018-07-08 11:47:01 +00:00
}
currentIndex = currentIndex + 1
}
})
2020-01-05 04:14:14 +00:00
return matchingNodes, errorReadingStream
2019-12-16 05:46:20 +00:00
}
func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
log.Debugf("done %v / %v", currentIndex, docIndexInt)
if !updateAll && currentIndex <= docIndexInt {
2020-01-06 03:22:24 +00:00
return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex)
2019-12-16 05:46:20 +00:00
}
return nil
}
2019-12-28 07:19:37 +00:00
func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) {
2019-12-16 05:46:20 +00:00
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
2019-12-25 01:11:04 +00:00
yqlib.DebugNode(&dataBucket)
2019-12-16 05:46:20 +00:00
if !updateAll && currentIndex != docIndexInt {
2019-12-22 06:13:11 +00:00
return originalMatchingNodes, nil
2019-12-16 05:46:20 +00:00
}
log.Debugf("reading %v in document %v", path, currentIndex)
2019-12-22 06:13:11 +00:00
matchingNodes, errorParsing := lib.Get(&dataBucket, path)
2019-12-16 05:46:20 +00:00
if errorParsing != nil {
return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
}
2019-12-22 06:13:11 +00:00
return append(originalMatchingNodes, matchingNodes...), nil
2019-12-16 05:46:20 +00:00
}
2019-12-22 22:08:00 +00:00
func printValue(node *yaml.Node, cmd *cobra.Command) error {
if node.Kind == yaml.ScalarNode {
cmd.Print(node.Value)
return nil
}
var encoder = yaml.NewEncoder(cmd.OutOrStdout())
encoder.SetIndent(2)
if err := encoder.Encode(node); err != nil {
return err
}
encoder.Close()
return nil
}
2019-12-28 07:19:37 +00:00
func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error {
2019-12-22 06:13:11 +00:00
if len(matchingNodes) == 0 {
2019-12-15 07:24:23 +00:00
log.Debug("no matching results, nothing to print")
return nil
}
2018-07-08 11:47:01 +00:00
2019-12-22 22:08:00 +00:00
for index, mappedDoc := range matchingNodes {
switch printMode {
case "k":
2020-01-09 10:18:24 +00:00
cmd.Print(lib.PathStackToString(mappedDoc.PathStack))
2019-12-22 22:08:00 +00:00
if index < len(matchingNodes)-1 {
cmd.Print("\n")
}
case "kv", "vk":
// put it into a node and print that.
var parentNode = yaml.Node{Kind: yaml.MappingNode}
parentNode.Content = make([]*yaml.Node, 2)
2020-01-09 10:18:24 +00:00
parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)}
2019-12-22 22:08:00 +00:00
parentNode.Content[1] = mappedDoc.Node
if err := printValue(&parentNode, cmd); err != nil {
2019-12-22 06:13:11 +00:00
return err
2019-12-06 04:57:46 +00:00
}
2019-12-22 22:08:00 +00:00
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")
}
2019-12-06 04:57:46 +00:00
}
}
2019-12-22 06:13:11 +00:00
return nil
}
2018-06-20 01:45:51 +00:00
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
}
2019-12-06 04:57:46 +00:00
type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error
2018-06-14 23:43:20 +00:00
func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn {
return func(decoder *yaml.Decoder) error {
2019-12-06 04:57:46 +00:00
var dataBucket yaml.Node
var errorReading error
var errorWriting error
2018-06-15 06:40:52 +00:00
var errorUpdating error
var currentIndex = 0
2018-06-20 01:45:51 +00:00
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 {
2018-07-08 11:47:01 +00:00
if !updateAll && currentIndex <= docIndexInt {
2019-01-20 22:33:14 +00:00
return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex)
}
return nil
} else if errorReading != nil {
2018-06-15 06:11:13 +00:00
return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading)
}
2019-12-06 04:57:46 +00:00
errorUpdating = updateData(&dataBucket, currentIndex)
2018-06-15 06:40:52 +00:00
if errorUpdating != nil {
return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex)
}
2019-12-06 04:57:46 +00:00
errorWriting = encoder.Encode(&dataBucket)
if errorWriting != nil {
2018-06-15 06:11:13 +00:00
return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting)
}
currentIndex = currentIndex + 1
}
}
}
func writeProperty(cmd *cobra.Command, args []string) error {
2019-12-06 05:36:42 +00:00
var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>")
if updateCommandsError != nil {
return updateCommandsError
2017-04-11 23:16:54 +00:00
}
2019-12-12 09:47:22 +00:00
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
2019-12-16 10:09:23 +00:00
func mergeProperties(cmd *cobra.Command, args []string) error {
2020-01-06 03:22:24 +00:00
if len(args) < 2 {
return errors.New("Must provide at least 2 yaml files")
}
2019-12-16 10:09:23 +00:00
// first generate update commands from the file
2020-01-05 04:14:14 +00:00
var filesToMerge = args[1:]
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
for _, fileToMerge := range filesToMerge {
matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0)
2020-01-09 10:27:52 +00:00
if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) {
2020-01-05 04:14:14 +00:00
return errorProcessingFile
}
for _, matchingNode := range matchingNodes {
2020-01-09 10:18:24 +00:00
mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag)
2020-01-05 04:14:14 +00:00
updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag})
}
}
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
2019-12-16 10:09:23 +00:00
}
2019-12-15 08:34:05 +00:00
func newProperty(cmd *cobra.Command, args []string) error {
2019-12-16 05:17:01 +00:00
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide <path_to_update> <value>")
2019-12-15 08:34:05 +00:00
if updateCommandsError != nil {
return updateCommandsError
}
2019-12-16 05:17:01 +00:00
newNode := lib.New(updateCommands[0].Path)
2019-12-15 08:34:05 +00:00
2019-12-16 05:17:01 +00:00
for _, updateCommand := range updateCommands {
2019-12-15 08:34:05 +00:00
2020-01-05 04:14:14 +00:00
errorUpdating := lib.Update(&newNode, updateCommand, true)
2019-12-15 08:34:05 +00:00
if errorUpdating != nil {
return errorUpdating
}
}
var encoder = yaml.NewEncoder(cmd.OutOrStdout())
encoder.SetIndent(2)
2020-01-09 10:27:52 +00:00
errorEncoding := encoder.Encode(&newNode)
2019-12-15 08:34:05 +00:00
encoder.Close()
2020-01-09 10:27:52 +00:00
return errorEncoding
2019-12-16 05:17:01 +00:00
}
func prefixProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <prefixed_path>")
}
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 {
2019-12-16 05:46:20 +00:00
return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
}
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
}
2019-12-16 05:17:01 +00:00
2019-12-16 05:46:20 +00:00
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)
2019-12-25 01:11:04 +00:00
yqlib.DebugNode(dataBucket)
2019-12-16 05:46:20 +00:00
updateCommand.Value = dataBucket.Content[0]
dataBucket.Content = make([]*yaml.Node, 1)
2019-12-16 05:17:01 +00:00
2019-12-16 05:46:20 +00:00
newNode := lib.New(updateCommand.Path)
dataBucket.Content[0] = &newNode
2019-12-16 05:17:01 +00:00
2020-01-05 04:14:14 +00:00
errorUpdating := lib.Update(dataBucket, updateCommand, true)
2019-12-16 05:46:20 +00:00
if errorUpdating != nil {
return errorUpdating
2019-12-16 05:17:01 +00:00
}
}
2019-12-16 05:46:20 +00:00
return nil
2019-12-15 08:34:05 +00:00
}
2019-12-12 09:47:22 +00:00
func deleteProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <path_to_delete>")
}
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 {
2018-06-20 01:45:51 +00:00
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
2019-12-06 04:57:46 +00:00
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
2018-06-20 01:45:51 +00:00
if updateAll || currentIndex == docIndexInt {
2018-06-14 23:43:20 +00:00
log.Debugf("Updating doc %v", currentIndex)
2019-12-06 05:36:42 +00:00
for _, updateCommand := range updateCommands {
2020-01-05 04:14:14 +00:00
log.Debugf("Processing update to Path %v", updateCommand.Path)
errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
2019-12-06 04:57:46 +00:00
if errorUpdating != nil {
return errorUpdating
}
2018-06-14 23:43:20 +00:00
}
}
2019-12-06 04:57:46 +00:00
return nil
2018-06-14 23:43:20 +00:00
}
2019-12-12 09:47:22 +00:00
return readAndUpdate(writer, inputFile, updateData)
2018-06-14 23:43:20 +00:00
}
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 {
2018-06-14 23:43:20 +00:00
var writer = bufio.NewWriter(stdOut)
destination = writer
destinationName = "Stdout"
defer safelyFlush(writer)
}
var encoder = yaml.NewEncoder(destination)
2019-12-06 04:57:46 +00:00
encoder.SetIndent(2)
log.Debugf("Writing to %v from %v", destinationName, inputFile)
2018-06-14 23:43:20 +00:00
return readStream(inputFile, mapYamlDecoder(updateData, encoder))
}
2019-12-16 05:17:01 +00:00
type updateCommandParsed struct {
Command string
Path string
Value yaml.Node
}
2019-12-06 05:36:42 +00:00
func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) {
2019-12-16 05:17:01 +00:00
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
if writeScript != "" {
2019-12-16 05:17:01 +00:00
var parsedCommands = make([]updateCommandParsed, 0)
if err := readData(writeScript, 0, &parsedCommands); err != nil {
return nil, err
}
2019-12-16 05:17:01 +00:00
log.Debugf("Read write commands file '%v'", parsedCommands)
for index := range parsedCommands {
parsedCommand := parsedCommands[index]
2020-01-05 04:14:14 +00:00
updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true}
2019-12-16 05:17:01 +00:00
updateCommands = append(updateCommands, updateCommand)
}
2019-12-06 05:36:42 +00:00
log.Debugf("Read write commands file '%v'", updateCommands)
2018-06-12 23:13:58 +00:00
} else if len(args) < expectedArgs {
return nil, errors.New(badArgsMessage)
} else {
2019-12-06 05:36:42 +00:00
updateCommands = make([]yqlib.UpdateCommand, 1)
2019-12-15 08:34:05 +00:00
log.Debug("args %v", args)
log.Debug("path %v", args[expectedArgs-2])
log.Debug("Value %v", args[expectedArgs-1])
2020-01-05 04:14:14 +00:00
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true}
2015-09-29 06:29:32 +00:00
}
2019-12-06 05:36:42 +00:00
return updateCommands, nil
2018-06-12 23:13:58 +00:00
}
2015-09-29 06:29:32 +00:00
func safelyRenameFile(from string, to string) {
2019-08-26 09:41:56 +00:00
if renameError := os.Rename(from, to); renameError != nil {
2019-12-06 04:57:46 +00:00
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
2019-08-26 09:41:56 +00:00
log.Debug(renameError.Error())
2018-06-27 02:06:31 +00:00
// 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)
2019-08-26 09:41:56 +00:00
log.Error(copyError.Error())
} else {
removeErr := os.Remove(from)
if removeErr != nil {
log.Errorf("failed removing original file: %s", from)
}
2018-06-27 02:06:31 +00:00
}
}
}
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
func copyFileContents(src, dst string) (err error) {
2018-08-06 06:24:06 +00:00
in, err := os.Open(src) // nolint gosec
2018-06-27 02:06:31 +00:00
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
}
2018-06-27 02:06:31 +00:00
return out.Sync()
}
func safelyFlush(writer *bufio.Writer) {
if err := writer.Flush(); err != nil {
log.Error("Error flushing writer!")
log.Error(err.Error())
}
}
2018-06-12 05:33:59 +00:00
func safelyCloseFile(file *os.File) {
err := file.Close()
if err != nil {
log.Error("Error closing file!")
log.Error(err.Error())
2018-06-12 05:33:59 +00:00
}
}
type yamlDecoderFn func(*yaml.Decoder) error
func readStream(filename string, yamlDecoder yamlDecoderFn) error {
if filename == "" {
return errors.New("Must provide filename")
2015-09-28 02:00:38 +00:00
}
2015-10-05 04:48:34 +00:00
2018-06-12 05:33:59 +00:00
var stream io.Reader
if filename == "-" {
2018-06-12 05:33:59 +00:00
stream = bufio.NewReader(os.Stdin)
2015-10-05 04:48:34 +00:00
} else {
2018-08-06 06:24:06 +00:00
file, err := os.Open(filename) // nolint gosec
2018-06-12 05:33:59 +00:00
if err != nil {
return err
}
defer safelyCloseFile(file)
stream = file
2015-10-05 04:48:34 +00:00
}
return yamlDecoder(yaml.NewDecoder(stream))
}
2015-09-26 22:20:42 +00:00
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 {
2018-06-15 06:11:13 +00:00
return errors.Wrapf(errorSkipping, "Error processing document at index %v, %v", currentIndex, errorSkipping)
}
2018-06-12 05:33:59 +00:00
}
return decoder.Decode(parsedData)
})
2015-10-06 05:39:19 +00:00
}