Edit complex files in place
By default edits JSON/YAML files, but supports more formats with "lenses".
Lenses can be nested. You can in-place "edit" a json field encoded as a string in a TOML file that in turn is embedded in a string literal of a YAML file.
brew install kubecfg/lensed/lensed
go install github.com/kubecfg/lensed@latest
Let's start with a field replacement in YAML files.
It can operate in a pipeline:
$ cat >example.yaml <<EOF
foo:
bar: old
EOF
$ lensed set /foo/bar=new <example.yaml
foo:
bar: new
or modify files in place (like `sed -i``):
$ cat >example.yaml <<EOF
foo:
bar: old
EOF
$ lensed set /foo/bar=new -f example.yaml
$ cat example.yaml
foo:
bar: new
It preserves comments:
$ cat >example.yaml <<EOF
foo:
bar: old # comment preserved
EOF
$ lensed set /foo/bar=new <example.yaml
foo:
bar: new # comment preserved
and other stylistic choices from the source file:
$ cat >example.yaml <<EOF
foo:
bar: 'old'
EOF
$ lensed set /foo/bar=new <example.yaml
foo:
bar: 'new'
$ cat >example.yaml <<EOF
foo:
bar: "old"
EOF
$ lensed set /foo/bar=new <example.yaml
foo:
bar: "new"
Once you reached a string field, you can "dig deeper" and interpret the content of that string as another hierarchical text format, and address other fields inside that inner string:
$ cat >example.yaml <<EOF
foo: |
[bar]
baz = "old"
EOF
$ lensed set "/foo/~(toml)/bar/baz=new" <example.yaml
foo: |
[bar]
baz = "new"
The edits in the inner text are properly escaped back.
$ cat >example.yaml <<EOF
foo: "bar = \"I'm old\""
EOF
$ lensed set "/foo/~(toml)/bar=I'm \"quoted\"" -f example.yaml
$ cat example.yaml
foo: "bar = \"I'm \\\"quoted\\\"\""
$ lensed get "/foo/~(toml)/bar" < example.yaml
I'm "quoted"
Supported lenses:
- yaml (also json)
- yamls (yaml stream)
- toml
- base64
- line
- regexp
- oci (alias ociImageRef)
- jsonnet
Decoding a base64 field from a YAML file is not hard:
$ cat >example.yaml <<EOF
apiVersion: v1
kind: Secret
data:
foo: eyJwYXNzd29yZCI6ICAgICAiaHVudGVyMTIifQ==
EOF
$ lensed get "/data/foo/~(base64)" <example.yaml
{"password": "hunter12"}
But editing a field inside of a base64 encoded JSON inside a YAML file, it's a breeze with lensed
:
$ lensed set "/data/foo/~(base64)/~(yaml)/password=newpassword" -f example.yaml
$ cat example.yaml
apiVersion: v1
kind: Secret
data:
foo: eyJwYXNzd29yZCI6ICAgICAibmV3cGFzc3dvcmQifQ==
$ lensed get "/data/foo/~(base64)" <example.yaml
{"password": "newpassword"}
(notice how the original formatting of the embedded value is preserved, despite the various decoding/encoding roundtrips)
This example also shows that you can edit a top-level format that is not YAML.
All you need to do is to start the JSONPointer with ~(<lensname>)
:
$ cat >example.yaml <<EOF
{
foo+: {
bar: "old",
}
}
EOF
$ lensed get "~(jsonnet)/foo/bar" <example.yaml
old
$ lensed set "~(jsonnet)/foo/bar=new" <example.yaml
{
foo+: {
bar: "new",
}
}
It can also edit the urls inside imports:
$ cat >example.yaml <<EOF
{
foo+: importstr "old.yaml",
}
EOF
$ lensed get "~(jsonnet)/foo/~file" <example.yaml
old
$ lensed set "~(jsonnet)/foo/~file=new.yaml" <example.yaml
{
foo+: importstr "new.yaml",
}
It can only handle leaf nodes. i.e. you can't replace a yaml subtree with another yaml subtree