Skip to content

Commit

Permalink
✨ Added json extended encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinemartin committed Jan 24, 2023
1 parent e57dcf5 commit 10cb26b
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 22 deletions.
122 changes: 102 additions & 20 deletions pkg/extras/extender.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package extras
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"regexp"
"strconv"
Expand All @@ -12,6 +13,7 @@ import (
"golang.org/x/text/language"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

Expand Down Expand Up @@ -45,6 +47,7 @@ const (
YamlExtender
Base64Extender
RegexExtender
JsonExtender
)

var stringToExtenderTypeMap map[string]ExtenderType
Expand Down Expand Up @@ -89,8 +92,7 @@ type yamlExtender struct {
node *yaml.RNode
}

func (e *yamlExtender) SetPayload(payload []byte) error {

func readPayload(payload []byte) (*yaml.RNode, error) {
nodes, err := (&kio.ByteReader{
Reader: bytes.NewBuffer(payload),
OmitReaderAnnotations: false,
Expand All @@ -99,28 +101,36 @@ func (e *yamlExtender) SetPayload(payload []byte) error {
}).Read()

if err != nil {
return errors.WrapPrefixf(err, "while reading yaml payload")
return nil, errors.WrapPrefixf(err, "while reading payload")
}
e.node = nodes[0]
return nil
return nodes[0], nil
}

func (e *yamlExtender) GetPayload() ([]byte, error) {
func (e *yamlExtender) SetPayload(payload []byte) (err error) {
e.node, err = readPayload(payload)
return
}

func getNodeBytes(nodes []*yaml.RNode) ([]byte, error) {
var b bytes.Buffer
err := (&kio.ByteWriter{Writer: &b}).Write([]*yaml.RNode{e.node})
err := (&kio.ByteWriter{Writer: &b}).Write(nodes)
return b.Bytes(), err
}

func (e *yamlExtender) LookupNode() *yaml.RNode {
seqNode, err := e.node.Pipe(yaml.Lookup(yaml.BareSeqNodeWrappingKey))
func (e *yamlExtender) GetPayload() ([]byte, error) {
return getNodeBytes([]*yaml.RNode{e.node})
}

func LookupNode(node *yaml.RNode) *yaml.RNode {
seqNode, err := node.Pipe(yaml.Lookup(yaml.BareSeqNodeWrappingKey))
if err == nil && !seqNode.IsNilOrEmpty() {
return seqNode
}
return e.node
return node
}

func (e *yamlExtender) Lookup(path []string) ([]*yaml.RNode, error) {
node, err := e.LookupNode().Pipe(&yaml.PathMatcher{Path: path})
func Lookup(node *yaml.RNode, path []string) ([]*yaml.RNode, error) {
node, err := LookupNode(node).Pipe(&yaml.PathMatcher{Path: path})
if err != nil {
return nil, errors.WrapPrefixf(err, "while getting path %s", strings.Join(path, "."))
}
Expand All @@ -129,7 +139,7 @@ func (e *yamlExtender) Lookup(path []string) ([]*yaml.RNode, error) {
}

func (e *yamlExtender) Get(path []string) ([]byte, error) {
targetFields, err := e.Lookup(path)
targetFields, err := Lookup(e.node, path)
if err != nil {
return nil, fmt.Errorf("error fetching elements in replacement target: %w", err)
}
Expand All @@ -138,17 +148,12 @@ func (e *yamlExtender) Get(path []string) ([]byte, error) {
return []byte(targetFields[0].YNode().Value), nil
}

var b bytes.Buffer
err = (&kio.ByteWriter{Writer: &b}).Write(targetFields)
if err != nil {
return nil, errors.WrapPrefixf(err, "while serializing path %s", strings.Join(path, "."))
}
return b.Bytes(), err
return getNodeBytes(targetFields)
}

func (e *yamlExtender) Set(path []string, value any) error {

targetFields, err := e.Lookup(path)
targetFields, err := Lookup(e.node, path)
if err != nil {
return fmt.Errorf("error fetching elements in replacement target: %w", err)
}
Expand Down Expand Up @@ -280,6 +285,82 @@ func NewRegexExtender() Extender {
return &regexExtender{}
}

///////
// JSON
///////

type jsonExtender struct {
node *yaml.RNode
}

func (e *jsonExtender) SetPayload(payload []byte) (err error) {
e.node, err = readPayload(payload)
return
}

func getJSONPayload(node *yaml.RNode) ([]byte, error) {
var b bytes.Buffer
if node.YNode().Kind == yaml.MappingNode {
node = node.Copy()
node.Pipe(yaml.ClearAnnotation(kioutil.IndexAnnotation))
node.Pipe(yaml.ClearAnnotation(kioutil.LegacyIndexAnnotation))
node.Pipe(yaml.ClearAnnotation(kioutil.SeqIndentAnnotation))
yaml.ClearEmptyAnnotations(node)
}
encoder := json.NewEncoder(&b)
encoder.SetIndent("", " ")
err := errors.Wrap(encoder.Encode(node))

return b.Bytes(), err
}

func (e *jsonExtender) GetPayload() ([]byte, error) {
return getJSONPayload(LookupNode(e.node))
}

func (e *jsonExtender) Get(path []string) ([]byte, error) {
targetFields, err := Lookup(e.node, path)
if err != nil {
return nil, errors.WrapPrefixf(err, "error fetching elements in replacement target")
}

if len(targetFields) > 1 {
return nil, fmt.Errorf("path %s returned %d items. Expected one", strings.Join(path, "."), len(targetFields))
}

target := targetFields[0]
if target.YNode().Kind == yaml.ScalarNode {
return []byte(target.YNode().Value), nil
}

return getJSONPayload(target)
}

func (e *jsonExtender) Set(path []string, value any) error {

targetFields, err := Lookup(e.node, path)
if err != nil {
return fmt.Errorf("error fetching elements in replacement target: %w", err)
}

for _, t := range targetFields {
if t.YNode().Kind == yaml.ScalarNode {
t.YNode().Value = string(GetByteValue(value))
} else {
if v, ok := value.(*yaml.Node); ok {
t.SetYNode(v)
} else {
return fmt.Errorf("setting non json object in place of object of type %s at path %s", t.YNode().Tag, strings.Join(path, "."))
}
}
}
return nil
}

func NewJsonExtender() Extender {
return &jsonExtender{}
}

////////////
// Factories
////////////
Expand All @@ -288,6 +369,7 @@ var ExtenderFactories = map[ExtenderType]func() Extender{
YamlExtender: NewYamlExtender,
Base64Extender: NewBase64Extender,
RegexExtender: NewRegexExtender,
JsonExtender: NewJsonExtender,
}

func (path *ExtendedSegment) Extender(payload []byte) (Extender, error) {
Expand Down
100 changes: 100 additions & 0 deletions pkg/extras/extender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,106 @@ common:
require.Equal(expected, b.String(), sString, "replacement failed")
}

func (s *ExtenderTestSuite) TestJsonExtender() {
require := s.Require()
source := `{
"common": {
"targetRevision": "main"
},
"uninode": true,
"apps": {
"enabled": true
}
}`
expected := `{
"apps": {
"enabled": true
},
"common": {
"targetRevision": "deploy/citest"
},
"uninode": true
}
`

p := `!!json.common.targetRevision`
path := kyaml_utils.SmarterPathSplitter(p, ".")

extensions := []*ExtendedSegment{}
prefix, err := splitExtendedPath(path, &extensions)
require.NoError(err)
require.Len(prefix, 0, "There should be no prefix")
require.Len(extensions, 1, "There should be 2 extensions")
require.Equal("json", extensions[0].Encoding, "The first extension should be json")

jsonXP := extensions[0]
jsonExt, err := jsonXP.Extender([]byte(source))
require.NoError(err)
value, err := jsonExt.Get(jsonXP.Path)
require.NoError(err)
require.Equal("main", string(value), "error fetching value")
require.NoError(jsonExt.Set(jsonXP.Path, []byte("deploy/citest")))

modified, err := jsonExt.GetPayload()
require.NoError(err)
require.Equal(expected, string(modified), "final json")

value, err = jsonExt.Get(jsonXP.Path)
require.NoError(err)
require.Equal("deploy/citest", string(value), "error fetching changed value")
}

func (s *ExtenderTestSuite) TestJsonArrayExtender() {
require := s.Require()
source := `[
{
"name": "targetRevision",
"value": "main"
},
{
"name": "repoURL",
"value": "https://github.com/kaweezle/example.git"
}
]`
expected := `[
{
"name": "targetRevision",
"value": "deploy/citest"
},
{
"name": "repoURL",
"value": "https://github.com/kaweezle/example.git"
}
]
`

p := `!!json.[name=targetRevision].value`
path := kyaml_utils.SmarterPathSplitter(p, ".")

extensions := []*ExtendedSegment{}
prefix, err := splitExtendedPath(path, &extensions)
require.NoError(err)
require.Len(prefix, 0, "There should be no prefix")
require.Len(extensions, 1, "There should be 2 extensions")
require.Equal("json", extensions[0].Encoding, "The first extension should be json")

jsonXP := extensions[0]
jsonExt, err := jsonXP.Extender([]byte(source))
require.NoError(err)
value, err := jsonExt.Get(jsonXP.Path)
require.NoError(err)
require.Equal("main", string(value), "error fetching value")
require.NoError(jsonExt.Set(jsonXP.Path, []byte("deploy/citest")))

modified, err := jsonExt.GetPayload()
require.NoError(err)
require.Equal(expected, string(modified), "final json")

value, err = jsonExt.Get(jsonXP.Path)
require.NoError(err)
require.Equal("deploy/citest", string(value), "error fetching changed value")
}

func TestExtender(t *testing.T) {
suite.Run(t, new(ExtenderTestSuite))
}
5 changes: 3 additions & 2 deletions pkg/extras/extendertype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 10cb26b

Please sign in to comment.