Skip to content

Commit

Permalink
Merge pull request #5 from VojtechVitek/next
Browse files Browse the repository at this point in the history
Next: v0.0.5 features
  • Loading branch information
VojtechVitek authored Nov 7, 2019
2 parents e711941 + b37574f commit 51f4900
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 74 deletions.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# YAML CLI processor <!-- omit in toc -->
A CLI tool for querying and transforming YAML data: Grep matching objects, join YAML documents, get/add/edit/delete YAML nodes matching given selector, loop over objects and/or data arrays etc.
# Streaming YAML CLI processor <!-- omit in toc -->
A CLI tool for querying and transforming YAML stream data:
- Grep matching documents (ie. K8s objects)
- Join multiple YAML files
- Get/add/edit/delete YAML nodes matching given selector
- Loop over documents and/or data arrays
- etc.

`[input.yml] => [query or transformations] => [output.yml]`

*Note: The input YAML data might contain multiple YAML documents separated by `---`.*
*Note: The input YAML documents in a [YAML stream](https://yaml.org/spec/1.2/spec.html#id2801681) are separated by `---`.*

- [One-liner commands](#one-liner-commands)
- [yaml get selector](#yaml-get-selector)
- [yaml get selector --print-key](#yaml-get-selector---print-key)
- [yaml get array[*] --print-key](#yaml-get-array---print-key)
- [yaml get spec.containers[*].image --no-separator](#yaml-get-speccontainersimage---no-separator)
- [yaml set "selector: value"](#yaml-set-%22selector-value%22)
- [yaml default "selector: value"](#yaml-default-%22selector-value%22)
- [yaml delete selector](#yaml-delete-selector)
Expand Down Expand Up @@ -42,6 +50,8 @@ $ kubectl get pod/nats-8576dfb67-vg6v7 -o yaml | yaml get spec.containers[0].ima
nats-streaming:0.10.0
```

### yaml get selector --print-key

Since we're printing a value, the output might not necesarilly be a valid YAML.

If we're printing value of a primitive type (ie. string) and we need the output in a valid YAML format, so it can be processed further, we can explicitly print the node key in front of the value:
Expand All @@ -60,6 +70,8 @@ image: nats-streaming:0.10.0
image: sidecar:1.0.1
```

### yaml get array[*] --print-key

We can print all array items at once with a wildcard (`array[*]`) too:

```bash
Expand All @@ -69,7 +81,9 @@ image: nats-streaming:0.10.0
image: sidecar:1.0.1
```

Need to get list of values only?
### yaml get spec.containers[*].image --no-separator

Need to print values only?

```bash
$ kubectl get pod/nats-8576dfb67-vg6v7 -o yaml | yaml get spec.containers[*].image --no-separator
Expand Down
2 changes: 1 addition & 1 deletion delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"testing"

"github.com/VojtechVitek/yaml"
"github.com/VojtechVitek/yaml-cli"
"github.com/google/go-cmp/cmp"
)

Expand Down
21 changes: 17 additions & 4 deletions match.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package yaml

import (
"fmt"
"regexp"
"strings"

Expand All @@ -10,12 +11,24 @@ import (

func (t *Transformation) MustMatchAll(doc *yaml.Node) (bool, error) {
for path, want := range t.Matches {
if want.Kind != yaml.ScalarNode {
return false, errors.Errorf("TODO: Support non-scalar match values?")
var regexString string

switch want.Kind {
case yaml.ScalarNode: // Single value. Ok.
regexString = want.Value
case yaml.SequenceNode: // Obsolete syntax, ie. "kind: [Deployment, Pod]". Convert to regex.
var values []string
for _, node := range want.Content {
values = append(values, node.Value)
}
regexString = fmt.Sprintf("^%v$", strings.Join(values, "|"))
default:
panic(errors.Errorf("Unexpected match kind %v (expected: single value or array)", want.Kind))
}
re, err := regexp.Compile(want.Value)

re, err := regexp.Compile(regexString)
if err != nil {
return false, errors.Errorf("%q is not a valid regex, see https://github.com/google/re2/wiki/Syntax", want.Content)
return false, errors.Errorf("%q is not a valid regex, see https://github.com/google/re2/wiki/Syntax", regexString)
}

selectors := strings.Split(path, ".")
Expand Down
42 changes: 28 additions & 14 deletions pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import (

var (
// TODO: Move these vars to a struct, they shouldn't be global. A left-over from "main" pkg.
flags = flag.NewFlagSet("yaml", flag.ExitOnError)
from = flags.String("from", "yaml", "input data format [json]")
to = flags.String("to", "yaml", "output data format [json]")
printKey = flags.Bool("print-key", false, "yaml get: print node key in front of the value, so the output is valid YAML")
noSeparator = flags.Bool("no-separator", false, "yaml get: don't print `---' separator between YAML documents")
invert = flags.BoolP("invert-match", "v", false, "yaml grep -v: select non-matching documents")
flags = flag.NewFlagSet("yaml", flag.ExitOnError)
from = flags.String("from", "yaml", "input data format [json]")
to = flags.String("to", "yaml", "output data format [json]")
ignoreMissing = flags.Bool("ignore-missing", false, "yaml get: ignore missing nodes")
printKey = flags.Bool("print-key", false, "yaml get: print node key in front of the value, so the output is valid YAML")
noSeparator = flags.Bool("no-separator", false, "yaml get: don't print `---' separator between YAML documents")
invert = flags.BoolP("invert-match", "v", false, "yaml grep -v: select non-matching documents")
)

// TODO: Split into multiple files/functions. This function grew too much
Expand Down Expand Up @@ -237,19 +238,21 @@ func run(out io.Writer, in io.Reader, args []string) error {
return nil

case "get":
useTopLevelEnc := true

var allMatchedNodes []*yamlv3.Node

for _, selector := range args[1:] {
selectors := strings.Split(selector, ".")
lastSelector := selectors[len(selectors)-1]

nodes, err := yaml.Get(&doc, selectors)
if err != nil {
return errors.Wrapf(err, "failed to get %q", selector)
if !*ignoreMissing {
return errors.Wrapf(err, "failed to get %q", selector)
}
}

// // Don't reuse top level encoder; we don't want to render
// // multiple YAML documents separated by `---`.
// enc = yamlv3.NewEncoder(out)
// enc.SetIndent(2)
for _, node := range nodes {
if *printKey {
node = &yamlv3.Node{
Expand All @@ -265,10 +268,21 @@ func run(out io.Writer, in io.Reader, args []string) error {
}
}

if *noSeparator {
enc = yamlv3.NewEncoder(out)
enc.SetIndent(2)
allMatchedNodes = append(allMatchedNodes, node)
}
}

for _, node := range allMatchedNodes {
if useTopLevelEnc {
useTopLevelEnc = false
// Top level enc will print separator between documents.
if err := enc.Encode(node); err != nil {
return errors.Wrap(err, "failed to encode YAML node")
}
} else {
// Omit separator between one YAML document's nodes.
enc := yamlv3.NewEncoder(out)
enc.SetIndent(2)
if err := enc.Encode(node); err != nil {
return errors.Wrap(err, "failed to encode YAML node")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strings"
"testing"

"github.com/VojtechVitek/yaml/pkg/cli"
"github.com/VojtechVitek/yaml-cli/pkg/cli"
"github.com/google/go-cmp/cmp"
)

Expand Down
43 changes: 41 additions & 2 deletions pkg/cli/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
func TestGet(t *testing.T) {
var kubectlGetPod = readFile("_testfiles/kubectl-get-pod.yml")

// On single input object/document, get and print should behave the same.

tt := []*cliTestCase{
{
in: kubectlGetPod,
Expand Down Expand Up @@ -46,3 +44,44 @@ func TestGet(t *testing.T) {
tc.runTest(t)
}
}

func TestGetPrintKey(t *testing.T) {
var kubectlGetPod = readFile("_testfiles/kubectl-get-pod.yml")

tt := []*cliTestCase{
{
in: kubectlGetPod,
args: []string{"get", "--print-key", "status.containerStatuses[0].name"},
out: "name: goose-metrixdb\n",
},
{
in: kubectlGetPod,
args: []string{"get", "--print-key", "status.containerStatuses[0].state.terminated.finishedAt"},
out: "finishedAt: \"2019-08-18T12:23:29Z\"\n",
},
{
in: kubectlGetPod,
args: []string{"get", "--print-key", "status.containerStatuses[1].name"},
out: "name: linkerd-proxy\n",
},
{
in: kubectlGetPod,
args: []string{"get", "--print-key", "status.containerStatuses[1].state.running.startedAt"},
out: "startedAt: \"2019-08-18T12:23:30Z\"\n",
},
{
in: kubectlGetPod,
args: []string{"get", "--print-key", "status.containerStatuses[2].name"},
err: true,
},
{
in: kubectlGetPod,
args: []string{"get", "--print-key", "status.containerStatuses[2].state.terminated.finishedAt"},
err: true,
},
}

for _, tc := range tt {
tc.runTest(t)
}
}
48 changes: 0 additions & 48 deletions pkg/cli/print_test.go

This file was deleted.

0 comments on commit 51f4900

Please sign in to comment.