From 10cb26b91bfc42746a409e29ddb3676959e2ceb9 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 24 Jan 2023 16:24:53 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Added=20json=20extended=20encoding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/extras/extender.go | 122 +++++++++++++++++++++++++----- pkg/extras/extender_test.go | 100 ++++++++++++++++++++++++ pkg/extras/extendertype_string.go | 5 +- 3 files changed, 205 insertions(+), 22 deletions(-) diff --git a/pkg/extras/extender.go b/pkg/extras/extender.go index abb9851..509f2db 100644 --- a/pkg/extras/extender.go +++ b/pkg/extras/extender.go @@ -3,6 +3,7 @@ package extras import ( "bytes" "encoding/base64" + "encoding/json" "fmt" "regexp" "strconv" @@ -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" ) @@ -45,6 +47,7 @@ const ( YamlExtender Base64Extender RegexExtender + JsonExtender ) var stringToExtenderTypeMap map[string]ExtenderType @@ -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, @@ -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, ".")) } @@ -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) } @@ -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) } @@ -280,6 +285,82 @@ func NewRegexExtender() Extender { return ®exExtender{} } +/////// +// 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 //////////// @@ -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) { diff --git a/pkg/extras/extender_test.go b/pkg/extras/extender_test.go index f479a8f..b19973e 100644 --- a/pkg/extras/extender_test.go +++ b/pkg/extras/extender_test.go @@ -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)) } diff --git a/pkg/extras/extendertype_string.go b/pkg/extras/extendertype_string.go index a613560..4188249 100644 --- a/pkg/extras/extendertype_string.go +++ b/pkg/extras/extendertype_string.go @@ -12,11 +12,12 @@ func _() { _ = x[YamlExtender-1] _ = x[Base64Extender-2] _ = x[RegexExtender-3] + _ = x[JsonExtender-4] } -const _ExtenderType_name = "UnknownYamlExtenderBase64ExtenderRegexExtender" +const _ExtenderType_name = "UnknownYamlExtenderBase64ExtenderRegexExtenderJsonExtender" -var _ExtenderType_index = [...]uint8{0, 7, 19, 33, 46} +var _ExtenderType_index = [...]uint8{0, 7, 19, 33, 46, 58} func (i ExtenderType) String() string { if i < 0 || i >= ExtenderType(len(_ExtenderType_index)-1) {