Skip to content

Commit

Permalink
Add a patch filter for applying patches
Browse files Browse the repository at this point in the history
  • Loading branch information
jgustie committed Feb 9, 2024
1 parent 966d712 commit e4dba31
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 1 deletion.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/thestormforge/konjure
go 1.21

require (
github.com/evanphx/json-patch/v5 v5.8.1
github.com/fatih/color v1.16.0
github.com/google/go-jsonnet v0.20.0
github.com/google/uuid v1.6.0
Expand All @@ -16,6 +17,7 @@ require (
golang.org/x/sync v0.6.0
k8s.io/kube-openapi v0.0.0-20230601164746-7562a1006961
sigs.k8s.io/kustomize/kyaml v0.16.0
sigs.k8s.io/yaml v1.4.0
)

require (
Expand All @@ -40,5 +42,4 @@ require (
golang.org/x/sys v0.16.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/evanphx/json-patch/v5 v5.8.1 h1:iPEdwg0XayoS+E7Mth9JxwUtOgyVxnDTXHtKhZPlZxA=
github.com/evanphx/json-patch/v5 v5.8.1/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
Expand Down
89 changes: 89 additions & 0 deletions pkg/filters/patch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package filters

import (
"bytes"
"fmt"

jsonpatch "github.com/evanphx/json-patch/v5"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
yaml2 "sigs.k8s.io/yaml"
)

// UnsupportedPatchError is raised when a patch format is not recognized.
type UnsupportedPatchError struct {
PatchType string
}

func (e *UnsupportedPatchError) Error() string {
return fmt.Sprintf("unsupported patch type: %q", e.PatchType)
}

// PatchFilter is used to apply an arbitrary patch.
type PatchFilter struct {
// The media type of the patch being applied.
PatchType string
// The actual raw patch.
PatchData []byte
}

// Filter applies the configured patch.
func (f *PatchFilter) Filter(node *yaml.RNode) (*yaml.RNode, error) {
switch f.PatchType {
case "application/strategic-merge-patch+json", "strategic", "application/merge-patch+json", "merge", "":
// The patch is likely JSON, parse it as YAML and just clear the style
patchNode := yaml.NewRNode(&yaml.Node{})
if err := yaml.NewDecoder(bytes.NewReader(f.PatchData)).Decode(patchNode.YNode()); err != nil {
return nil, err
}
f.resetNodeStyle(patchNode.YNode())

// Strategic Merge/Merge Patch is just the merge2 logic
opts := yaml.MergeOptions{
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
}
return merge2.Merge(patchNode, node, opts)

case "application/json-patch+json", "json":
// The patch is likely JSON, but might be YAML that needs to be converted to JSON
patchData := f.PatchData
if !bytes.HasPrefix(patchData, []byte("[")) {
jsonData, err := yaml2.YAMLToJSON(patchData)
if err != nil {
return nil, err
}
patchData = jsonData
}
jsonPatch, err := jsonpatch.DecodePatch(patchData)
if err != nil {
return nil, err
}

// This is going to butcher the YAML ordering/comments/etc.
jsonData, err := node.MarshalJSON()
if err != nil {
return nil, err
}
jsonData, err = jsonPatch.Apply(jsonData)
if err != nil {
return nil, err
}
err = node.UnmarshalJSON(jsonData)
if err != nil {
return nil, err
}
return node, nil

default:
// This patch type is not supported
return nil, &UnsupportedPatchError{PatchType: f.PatchType}
}
}

// resetNodeStyle clears out the node style, this is useful to discard JSON formatting.
func (f *PatchFilter) resetNodeStyle(node *yaml.Node) {
node.Style = 0
for _, node := range node.Content {
f.resetNodeStyle(node)
}
}

0 comments on commit e4dba31

Please sign in to comment.