Add github.com/krishicks/yaml-patch to vendor

This commit is contained in:
Jingfang Liu
2018-08-30 13:45:25 -07:00
parent a81b2e32e0
commit 95cf508b2b
19 changed files with 1882 additions and 0 deletions

9
Gopkg.lock generated
View File

@@ -251,6 +251,14 @@
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
version = "1.1.3"
[[projects]]
digest = "1:10e1dfc240bcd0fce14366215fd2b070e79d9b9460b27382db4423ad74204f2b"
name = "github.com/krishicks/yaml-patch"
packages = ["."]
pruneopts = ""
revision = "83cc9ac50becbbfafb86a89167f3bc5372e8e530"
version = "v0.0.10"
[[projects]]
branch = "master"
digest = "1:d9e483f4b9e306facf126bd90b02d512bd22ea4471e1568867e32221a8abbb16"
@@ -490,6 +498,7 @@
"github.com/ghodss/yaml",
"github.com/golang/glog",
"github.com/hashicorp/go-getter",
"github.com/krishicks/yaml-patch",
"github.com/pkg/errors",
"github.com/spf13/cobra",
"k8s.io/api/core/v1",

View File

@@ -60,3 +60,7 @@
[[constraint]]
branch = "master"
name = "github.com/hashicorp/go-getter"
[[constraint]]
name = "github.com/krishicks/yaml-patch"
version = "0.0.10"

201
vendor/github.com/krishicks/yaml-patch/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

17
vendor/github.com/krishicks/yaml-patch/Makefile generated vendored Normal file
View File

@@ -0,0 +1,17 @@
CGO_ENABLED=0
all: windows linux darwin
linux:
GOOS=linux GOARCH=amd64 go build -o yaml_patch_linux cmd/yaml-patch/*.go
windows:
GOOS=windows GOARCH=amd64 go build -o yaml_patch.exe cmd/yaml-patch/*.go
darwin:
GOOS=darwin GOARCH=amd64 go build -o yaml_patch_darwin cmd/yaml-patch/*.go
clean:
rm yaml_patch_linux
rm yaml_patch.exe
rm yaml_patch_darwin

86
vendor/github.com/krishicks/yaml-patch/README.md generated vendored Normal file
View File

@@ -0,0 +1,86 @@
# yaml-patch
`yaml-patch` is a version of Evan Phoenix's
[json-patch](https://github.com/evanphx/json-patch), which is an implementation
of [JavaScript Object Notation (JSON) Patch](https://tools.ietf.org/html/rfc6902),
but for YAML.
## Installing
`go get github.com/krishicks/yaml-patch`
If you want to use the CLI:
`go get github.com/krishicks/yaml-patch/cmd/yaml-patch`
## API
Given the following RFC6902-ish YAML document, `ops`:
```
---
- op: add
path: /baz/waldo
value: fred
```
And the following YAML that is to be modified, `src`:
```
---
foo: bar
baz:
quux: grault
```
Decode the ops file into a patch:
```
patch, err := yamlpatch.DecodePatch(ops)
// handle err
```
Then apply that patch to the document:
```
dst, err := patch.Apply(src)
// handle err
// do something with dst
```
### Example
```
doc := []byte(`---
foo: bar
baz:
quux: grault
`)
ops := []byte(`---
- op: add
path: /baz/waldo
value: fred
`)
patch, err := yamlpatch.DecodePatch(ops)
if err != nil {
log.Fatalf("decoding patch failed: %s", err)
}
bs, err := patch.Apply(doc)
if err != nil {
log.Fatalf("applying patch failed: %s", err)
}
fmt.Println(string(bs))
```
```
baz:
quux: grault
waldo: fred
foo: bar
```

View File

@@ -0,0 +1,39 @@
package main
// Thanks, Concourse!
import (
"fmt"
"os"
"path/filepath"
)
// FileFlag is a flag for passing a path to a file on disk. The file is
// expected to be a file, not a directory, that actually exists.
type FileFlag string
// UnmarshalFlag implements go-flag's Unmarshaler interface
func (f *FileFlag) UnmarshalFlag(value string) error {
stat, err := os.Stat(value)
if err != nil {
return err
}
if stat.IsDir() {
return fmt.Errorf("path '%s' is a directory, not a file", value)
}
abs, err := filepath.Abs(value)
if err != nil {
return err
}
*f = FileFlag(abs)
return nil
}
// Path is the path to the file
func (f FileFlag) Path() string {
return string(f)
}

View File

@@ -0,0 +1,57 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
flags "github.com/jessevdk/go-flags"
yamlpatch "github.com/krishicks/yaml-patch"
)
type opts struct {
OpsFiles []FileFlag `long:"ops-file" short:"o" value-name:"PATH" description:"Path to file with one or more operations"`
}
func main() {
var o opts
_, err := flags.Parse(&o)
if err != nil {
log.Fatalf("error: %s\n", err)
}
placeholderWrapper := yamlpatch.NewPlaceholderWrapper("{{", "}}")
var patches []yamlpatch.Patch
for _, opsFile := range o.OpsFiles {
var bs []byte
bs, err = ioutil.ReadFile(opsFile.Path())
if err != nil {
log.Fatalf("error reading opsfile: %s", err)
}
var patch yamlpatch.Patch
patch, err = yamlpatch.DecodePatch(placeholderWrapper.Wrap(bs))
if err != nil {
log.Fatalf("error decoding opsfile: %s", err)
}
patches = append(patches, patch)
}
doc, err := ioutil.ReadAll(os.Stdin)
if err != nil {
log.Fatalf("error reading from stdin: %s", err)
}
mdoc := placeholderWrapper.Wrap(doc)
for _, patch := range patches {
mdoc, err = patch.Apply(mdoc)
if err != nil {
log.Fatalf("error applying patch: %s", err)
}
}
fmt.Printf("%s", placeholderWrapper.Unwrap(mdoc))
}

View File

@@ -0,0 +1,18 @@
package main_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"testing"
)
var _ = AfterSuite(func() {
gexec.CleanupBuildArtifacts()
})
func TestMain(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Main Suite")
}

View File

@@ -0,0 +1,14 @@
package main_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
var _ = Describe("yaml-patch", func() {
It("builds", func() {
_, err := gexec.Build("github.com/krishicks/yaml-patch/cmd/yaml-patch")
Expect(err).NotTo(HaveOccurred())
})
})

167
vendor/github.com/krishicks/yaml-patch/container.go generated vendored Normal file
View File

@@ -0,0 +1,167 @@
package yamlpatch
import (
"fmt"
"strconv"
"strings"
)
// Container is the interface for performing operations on Nodes
type Container interface {
Get(key string) (*Node, error)
Set(key string, val *Node) error
Add(key string, val *Node) error
Remove(key string) error
}
type nodeMap map[interface{}]*Node
func (n *nodeMap) Set(key string, val *Node) error {
(*n)[key] = val
return nil
}
func (n *nodeMap) Add(key string, val *Node) error {
(*n)[key] = val
return nil
}
func (n *nodeMap) Get(key string) (*Node, error) {
return (*n)[key], nil
}
func (n *nodeMap) Remove(key string) error {
_, ok := (*n)[key]
if !ok {
return fmt.Errorf("Unable to remove nonexistent key: %s", key)
}
delete(*n, key)
return nil
}
type nodeSlice []*Node
func (n *nodeSlice) Set(index string, val *Node) error {
i, err := strconv.Atoi(index)
if err != nil {
return err
}
sz := len(*n)
if i+1 > sz {
sz = i + 1
}
ary := make([]*Node, sz)
cur := *n
copy(ary, cur)
if i >= len(ary) {
return fmt.Errorf("Unable to access invalid index: %d", i)
}
ary[i] = val
*n = ary
return nil
}
func (n *nodeSlice) Add(index string, val *Node) error {
if index == "-" {
*n = append(*n, val)
return nil
}
i, err := strconv.Atoi(index)
if err != nil {
return err
}
ary := make([]*Node, len(*n)+1)
cur := *n
copy(ary[0:i], cur[0:i])
ary[i] = val
copy(ary[i+1:], cur[i:])
*n = ary
return nil
}
func (n *nodeSlice) Get(index string) (*Node, error) {
i, err := strconv.Atoi(index)
if err != nil {
return nil, err
}
if i >= 0 && i <= len(*n)-1 {
return (*n)[i], nil
}
return nil, fmt.Errorf("Unable to access invalid index: %d", i)
}
func (n *nodeSlice) Remove(index string) error {
i, err := strconv.Atoi(index)
if err != nil {
return err
}
cur := *n
if i >= len(cur) {
return fmt.Errorf("Unable to remove invalid index: %d", i)
}
ary := make([]*Node, len(cur)-1)
copy(ary[0:i], cur[0:i])
copy(ary[i:], cur[i+1:])
*n = ary
return nil
}
func findContainer(c Container, path *OpPath) (Container, string, error) {
parts, key, err := path.Decompose()
if err != nil {
return nil, "", err
}
foundContainer := c
for _, part := range parts {
node, err := foundContainer.Get(decodePatchKey(part))
if err != nil {
return nil, "", err
}
if node == nil {
return nil, "", fmt.Errorf("path does not exist: %s", path)
}
foundContainer = node.Container()
}
return foundContainer, decodePatchKey(key), nil
}
// From http://tools.ietf.org/html/rfc6901#section-4 :
//
// Evaluation of each reference token begins by decoding any escaped
// character sequence. This is performed by first transforming any
// occurrence of the sequence '~1' to '/', and then transforming any
// occurrence of the sequence '~0' to '~'.
var (
rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
)
func decodePatchKey(k string) string {
return rfc6901Decoder.Replace(k)
}

83
vendor/github.com/krishicks/yaml-patch/node.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
package yamlpatch
import "reflect"
// Node holds a YAML document that has not yet been processed into a NodeMap or
// NodeSlice
type Node struct {
raw *interface{}
container Container
}
// NewNode returns a new Node. It expects a pointer to an interface{}
func NewNode(raw *interface{}) *Node {
return &Node{
raw: raw,
}
}
// MarshalYAML implements yaml.Marshaler, and returns the correct interface{}
// to be marshaled
func (n *Node) MarshalYAML() (interface{}, error) {
if n.container != nil {
return n.container, nil
}
return *n.raw, nil
}
// UnmarshalYAML implements yaml.Unmarshaler
func (n *Node) UnmarshalYAML(unmarshal func(interface{}) error) error {
var data interface{}
err := unmarshal(&data)
if err != nil {
return err
}
n.raw = &data
return nil
}
// Empty returns whether the raw value is nil
func (n *Node) Empty() bool {
return *n.raw == nil
}
// Container returns the node as a Container
func (n *Node) Container() Container {
if n.container != nil {
return n.container
}
switch rt := (*n.raw).(type) {
case []interface{}:
c := make(nodeSlice, len(rt))
n.container = &c
for i := range rt {
c[i] = NewNode(&rt[i])
}
case map[interface{}]interface{}:
c := make(nodeMap, len(rt))
n.container = &c
for k := range rt {
v := rt[k]
c[k] = NewNode(&v)
}
}
return n.container
}
// Equal compares the values of the raw interfaces that the YAML was
// unmarshaled into
func (n *Node) Equal(other *Node) bool {
return reflect.DeepEqual(*n.raw, *other.raw)
}
// Value returns the raw value of the node
func (n *Node) Value() interface{} {
return *n.raw
}

181
vendor/github.com/krishicks/yaml-patch/operation.go generated vendored Normal file
View File

@@ -0,0 +1,181 @@
package yamlpatch
import (
"errors"
"fmt"
"strings"
)
// Op is a type alias
type Op string
// Ops
const (
opAdd Op = "add"
opRemove Op = "remove"
opReplace Op = "replace"
opMove Op = "move"
opCopy Op = "copy"
opTest Op = "test"
)
// OpPath is an RFC6902 'pointer'
type OpPath string
// Decompose returns the pointer's components:
// "/foo" => [], "foo"
// "/foo/1" => ["foo"], "1"
// "/foo/1/bar" => ["foo", "1"], "bar"
func (p *OpPath) Decompose() ([]string, string, error) {
path := string(*p)
if !strings.HasPrefix(path, "/") {
return nil, "", fmt.Errorf("operation path is missing leading '/': %s", path)
}
parts := strings.Split(path, "/")[1:]
return parts[:len(parts)-1], parts[len(parts)-1], nil
}
// ContainsExtendedSyntax returns whether the OpPath uses the "key=value"
// format, as in "/foo/name=bar", where /foo points at an array that contains
// an object with a key "name" that has a value "bar"
func (p *OpPath) ContainsExtendedSyntax() bool {
return strings.Contains(string(*p), "=")
}
// String returns the OpPath as a string
func (p *OpPath) String() string {
return string(*p)
}
// Operation is an RFC6902 'Operation'
// https://tools.ietf.org/html/rfc6902#section-4
type Operation struct {
Op Op `yaml:"op,omitempty"`
Path OpPath `yaml:"path,omitempty"`
From OpPath `yaml:"from,omitempty"`
Value *Node `yaml:"value,omitempty"`
}
// Perform executes the operation on the given container
func (o *Operation) Perform(c Container) error {
var err error
switch o.Op {
case opAdd:
err = tryAdd(c, o)
case opRemove:
err = tryRemove(c, o)
case opReplace:
err = tryReplace(c, o)
case opMove:
err = tryMove(c, o)
case opCopy:
err = tryCopy(c, o)
case opTest:
err = tryTest(c, o)
default:
err = fmt.Errorf("Unexpected op: %s", o.Op)
}
return err
}
func tryAdd(doc Container, op *Operation) error {
con, key, err := findContainer(doc, &op.Path)
if err != nil {
return fmt.Errorf("yamlpatch add operation does not apply: doc is missing path: %s", op.Path)
}
return con.Add(key, op.Value)
}
func tryRemove(doc Container, op *Operation) error {
con, key, err := findContainer(doc, &op.Path)
if err != nil {
return fmt.Errorf("yamlpatch remove operation does not apply: doc is missing path: %s", op.Path)
}
return con.Remove(key)
}
func tryReplace(doc Container, op *Operation) error {
con, key, err := findContainer(doc, &op.Path)
if err != nil {
return fmt.Errorf("yamlpatch replace operation does not apply: doc is missing path: %s", op.Path)
}
val, err := con.Get(key)
if val == nil || err != nil {
return fmt.Errorf("yamlpatch replace operation does not apply: doc is missing key: %s", op.Path)
}
return con.Set(key, op.Value)
}
func tryMove(doc Container, op *Operation) error {
con, key, err := findContainer(doc, &op.From)
if err != nil {
return fmt.Errorf("yamlpatch move operation does not apply: doc is missing from path: %s", op.From)
}
val, err := con.Get(key)
if err != nil {
return err
}
err = con.Remove(key)
if err != nil {
return err
}
con, key, err = findContainer(doc, &op.Path)
if err != nil {
return fmt.Errorf("yamlpatch move operation does not apply: doc is missing destination path: %s", op.Path)
}
return con.Set(key, val)
}
func tryCopy(doc Container, op *Operation) error {
con, key, err := findContainer(doc, &op.From)
if err != nil {
return fmt.Errorf("copy operation does not apply: doc is missing from path: %s", op.From)
}
val, err := con.Get(key)
if err != nil {
return err
}
con, key, err = findContainer(doc, &op.Path)
if err != nil {
return fmt.Errorf("copy operation does not apply: doc is missing destination path: %s", op.Path)
}
return con.Set(key, val)
}
func tryTest(doc Container, op *Operation) error {
con, key, err := findContainer(doc, &op.Path)
if err != nil {
return fmt.Errorf("test operation does not apply: doc is missing from path: %s", op.From)
}
val, err := con.Get(key)
if err != nil {
return err
}
if op.Value.Empty() && val == nil {
return nil
}
if op.Value.Equal(val) {
return nil
}
return errors.New("test failed")
}

60
vendor/github.com/krishicks/yaml-patch/patch.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
package yamlpatch
import (
"fmt"
yaml "gopkg.in/yaml.v2"
)
// Patch is an ordered collection of operations.
type Patch []Operation
// DecodePatch decodes the passed YAML document as if it were an RFC 6902 patch
func DecodePatch(bs []byte) (Patch, error) {
var p Patch
err := yaml.Unmarshal(bs, &p)
if err != nil {
return nil, err
}
return p, nil
}
// Apply returns a YAML document that has been mutated per the patch
func (p Patch) Apply(doc []byte) ([]byte, error) {
var iface interface{}
err := yaml.Unmarshal(doc, &iface)
if err != nil {
return nil, fmt.Errorf("failed unmarshaling doc: %s\n\n%s", string(doc), err)
}
var c Container
c = NewNode(&iface).Container()
for _, op := range p {
pathfinder := NewPathFinder(c)
if op.Path.ContainsExtendedSyntax() {
paths := pathfinder.Find(string(op.Path))
if paths == nil {
return nil, fmt.Errorf("could not expand pointer: %s", op.Path)
}
for _, path := range paths {
newOp := op
newOp.Path = OpPath(path)
err = newOp.Perform(c)
if err != nil {
return nil, err
}
}
} else {
err = op.Perform(c)
if err != nil {
return nil, err
}
}
}
return yaml.Marshal(c)
}

606
vendor/github.com/krishicks/yaml-patch/patch_test.go generated vendored Normal file
View File

@@ -0,0 +1,606 @@
package yamlpatch_test
import (
yamlpatch "github.com/krishicks/yaml-patch"
yaml "gopkg.in/yaml.v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
var _ = Describe("Patch", func() {
Describe("Apply", func() {
DescribeTable(
"positive cases",
func(doc, ops, expectedYAML string) {
patch, err := yamlpatch.DecodePatch([]byte(ops))
Expect(err).NotTo(HaveOccurred())
actualBytes, err := patch.Apply([]byte(doc))
Expect(err).NotTo(HaveOccurred())
var actualIface interface{}
err = yaml.Unmarshal(actualBytes, &actualIface)
Expect(err).NotTo(HaveOccurred())
var expectedIface interface{}
err = yaml.Unmarshal([]byte(expectedYAML), &expectedIface)
Expect(err).NotTo(HaveOccurred())
Expect(actualIface).To(Equal(expectedIface))
},
Entry("adding an element to an object",
`---
foo: bar
`,
`---
- op: add
path: /baz
value: qux
`,
`---
foo: bar
baz: qux
`,
),
Entry("adding an element to an array",
`---
foo: [bar,baz]
`,
`---
- op: add
path: /foo/1
value: qux
`,
`---
foo: [bar,qux,baz]
`,
),
Entry("removing an element from an object",
`---
foo: bar
baz: qux
`,
`---
- op: remove
path: /baz
`,
`---
foo: bar
`,
),
Entry("removing an element from an array",
`---
foo: [bar,qux,baz]
`,
`---
- op: remove
path: /foo/1
`,
`---
foo: [bar,baz]
`,
),
Entry("replacing an element in an object",
`---
foo: bar
baz: qux
`,
`---
- op: replace
path: /baz
value: boo
`,
`---
foo: bar
baz: boo
`,
),
Entry("moving an element in an object",
`---
foo:
bar: baz
waldo: fred
qux:
corge: grault
`,
`---
- op: move
from: /foo/waldo
path: /qux/thud
`,
`---
foo:
bar: baz
qux:
corge: grault
thud: fred
`,
),
Entry("moving an element in an array",
`---
foo: [all, grass, cows, eat]
`,
`---
- op: move
from: /foo/1
path: /foo/3
`,
`---
foo: [all, cows, eat, grass]
`,
),
Entry("adding an object to an object",
`---
foo: bar
`,
`---
- op: add
path: /child
value:
grandchild: {}
`,
`---
foo: bar
child:
grandchild: {}
`,
),
Entry("appending an element to an array",
`---
foo: [bar]
`,
`---
- op: add
path: /foo/-
value: [abc,def]
`,
`---
foo: [bar, [abc, def]]
`,
),
Entry("removing a nil element from an object",
`---
foo: bar
qux:
baz: 1
bar: ~
`,
`---
- op: remove
path: /qux/bar
`,
`---
foo: bar
qux:
baz: 1
`,
),
Entry("adding a nil element to an object",
`---
foo: bar
`,
`---
- op: add
path: /baz
value: ~
`,
`---
foo: bar
baz: ~
`,
),
Entry("replacing the sole element in an array",
`---
foo: [bar]
`,
`---
- op: replace
path: /foo/0
value: baz
`,
`---
foo: [baz]
`,
),
Entry("replacing an element in an array within a root array",
`---
- foo: [bar, qux, baz]
`,
`---
- op: replace
path: /0/foo/0
value: bum
`,
`---
- foo: [bum, qux, baz]
`,
),
Entry("copying an element in an array within a root array with an index",
`---
- foo: [bar, qux, baz]
bar: [qux, baz]
`,
`---
- op: copy
from: /0/foo/0
path: /0/bar/0
`,
`---
- foo: [bar, qux, baz]
bar: [bar, baz]
`,
),
Entry("testing for the existence of a nil value in an object",
`---
baz: ~
`,
`---
- op: test
path: /baz
value: ~
`,
`---
baz: ~
`,
),
Entry("testing for the existence of a nil key in an object",
`---
baz: ~
`,
`---
- op: test
path: /foo
value: ~
`,
`---
baz: ~
`,
),
Entry("testing for the existence of a nil value in an object",
`---
baz: qux
`,
`---
- op: test
path: /baz
value: qux
`,
`---
baz: qux
`,
),
Entry("testing for the existence of an element in an array",
`---
foo: [a, 2, c]
`,
`---
- op: test
path: /foo/1
value: 2
`,
`---
foo: [a, 2, c]
`,
),
Entry("testing for the existence of an element in an object using escape ordering",
`---
baz/foo: qux
`,
`---
- op: test
path: /baz~1foo
value: qux
`,
`---
baz/foo: qux
`,
),
Entry("testing for the existence of an object",
`---
foo:
- bar: baz
qux: corge
`,
`---
- op: test
path: /foo/0
value:
bar: baz
qux: corge
`,
`---
foo:
- bar: baz
qux: corge
`,
),
XEntry("copying an element in an array within a root array to a destination without an index",
// this is in jsonpatch, but I'd like confirmation from the spec that this is intended
`---
- foo: [bar, qux, baz]
bar: [qux, baz]
`,
`---
- op: copy
from: /0/foo/0
path: /0/bar
`,
`---
- foo: [bar, qux, baz]
bar: [bar, qux, baz]
`,
),
XEntry("copying an element in an array within a root array to a destination without an index",
`---
- foo:
bar: [qux, baz]
baz:
qux: bum
`,
`---
- op: copy
from: /0/foo/bar
path: /0/baz/bar
`,
`---
- foo: [bar, qux, baz]
bar: [bar, qux, baz]
`,
),
)
DescribeTable(
"with extended syntax",
func(doc, ops, expectedYAML string) {
patch, err := yamlpatch.DecodePatch([]byte(ops))
Expect(err).NotTo(HaveOccurred())
actualBytes, err := patch.Apply([]byte(doc))
Expect(err).NotTo(HaveOccurred())
var actualIface interface{}
err = yaml.Unmarshal(actualBytes, &actualIface)
Expect(err).NotTo(HaveOccurred())
var expectedIface interface{}
err = yaml.Unmarshal([]byte(expectedYAML), &expectedIface)
Expect(err).NotTo(HaveOccurred())
Expect(actualIface).To(Equal(expectedIface))
},
Entry("a path that begins with a composite key",
`---
- foo: bar
`,
`---
- op: replace
path: /foo=bar
value:
baz: quux
`,
`---
- baz: quux
`,
),
Entry("a path that begins with an array index and ends with a composite key",
`---
- waldo:
- thud: boo
- foo: bar
- corge: grault
`,
`---
- op: replace
path: /0/foo=bar
value:
baz: quux
`,
`---
- waldo:
- thud: boo
- baz: quux
- corge: grault
`,
),
Entry("a path that begins with an object key and ends with a composite key",
`---
waldo:
- thud: boo
- foo: bar
corge: grault
`,
`---
- op: replace
path: /waldo/foo=bar
value:
baz: quux
`,
`---
waldo:
- thud: boo
- baz: quux
corge: grault
`,
),
Entry("a path that doesn't end with a composite key",
`---
jobs:
- name: upgrade-opsmgr
serial: true
plan:
- get: pivnet-opsmgr
- put: something-else
`,
`---
- op: replace
path: /jobs/name=upgrade-opsmgr/plan/1
value:
get: something-else
`,
`---
jobs:
- name: upgrade-opsmgr
serial: true
plan:
- get: pivnet-opsmgr
- get: something-else
`,
),
Entry("removes multiple entries in a single op",
`---
foo:
- bar: baz
- waldo: fred
qux:
corge: grault
thud:
- waldo: fred
- bar: baz
`,
`---
- op: remove
path: /waldo=fred
`,
`---
foo:
- bar: baz
qux:
corge: grault
thud:
- bar: baz
`,
),
)
DescribeTable(
"failure cases",
func(doc, ops string) {
patch, err := yamlpatch.DecodePatch([]byte(ops))
Expect(err).NotTo(HaveOccurred())
_, err = patch.Apply([]byte(doc))
Expect(err).To(HaveOccurred())
},
Entry("adding an element to an object with a bad pointer",
`---
foo: bar
`,
`---
- op: add
path: /baz/bat
value: qux
`,
),
Entry("removing an element from an object with a bad pointer",
`---
a:
b:
d: 1
`,
`---
- op: remove
path: /a/b/c
`,
),
Entry("moving an element in an object with a bad pointer",
`---
a:
b:
d: 1
`,
`---
- op: move
from: /a/b/c
path: /a/b/e
`,
),
Entry("removing an element from an array with a bad pointer",
`---
a:
b: [1]
`,
`---
- op: remove
path: /a/b/1
`,
),
Entry("moving an element from an array with a bad pointer",
`---
a:
b: [1]
`,
`---
- op: move
from: /a/b/1
path: /a/b/2
`,
),
Entry("an operation with an invalid pathz field",
`---
foo: bar
`,
`---
- op: add
pathz: /baz
value: qux
`,
),
Entry("an add operation with an empty path",
`---
foo: bar
`,
`---
- op: add
path: ''
value: qux
`,
),
Entry("a replace operation on an array with an invalid path",
`---
name:
foo:
bat
qux:
bum
`,
`---
- op: replace
path: /foo/2
value: bum
`,
),
)
})
Describe("DecodePatch", func() {
It("returns an empty patch when given nil", func() {
patch, err := yamlpatch.DecodePatch(nil)
Expect(err).NotTo(HaveOccurred())
Expect(patch).To(HaveLen(0))
})
It("returns a patch with a single op when given a single op", func() {
ops := []byte(
`---
- op: add
path: /baz
value: qux`)
patch, err := yamlpatch.DecodePatch(ops)
Expect(err).NotTo(HaveOccurred())
var v interface{} = "qux"
value := yamlpatch.NewNode(&v)
Expect(patch).To(Equal(yamlpatch.Patch{
{
Op: "add",
Path: "/baz",
Value: value,
},
}))
})
})
})

109
vendor/github.com/krishicks/yaml-patch/pathfinder.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
package yamlpatch
import (
"fmt"
"strings"
)
// PathFinder can be used to find RFC6902-standard paths given non-standard
// (key=value) pointer syntax
type PathFinder struct {
root Container
}
// NewPathFinder takes an interface that represents a YAML document and returns
// a new PathFinder
func NewPathFinder(container Container) *PathFinder {
return &PathFinder{
root: container,
}
}
// Find expands the given path into all matching paths, returning the canonical
// versions of those matching paths
func (p *PathFinder) Find(path string) []string {
parts := strings.Split(path, "/")
if parts[1] == "" {
return []string{"/"}
}
routes := map[string]Container{
"": p.root,
}
for _, part := range parts[1:] {
routes = find(decodePatchKey(part), routes)
}
var paths []string
for k := range routes {
paths = append(paths, k)
}
return paths
}
func find(part string, routes map[string]Container) map[string]Container {
matches := map[string]Container{}
for prefix, container := range routes {
if part == "-" {
for k := range routes {
matches[fmt.Sprintf("%s/-", k)] = routes[k]
}
return matches
}
if kv := strings.Split(part, "="); len(kv) == 2 {
if newMatches := findAll(prefix, kv[0], kv[1], container); len(newMatches) > 0 {
matches = newMatches
}
continue
}
if node, err := container.Get(part); err == nil {
path := fmt.Sprintf("%s/%s", prefix, part)
if node == nil {
matches[path] = container
} else {
matches[path] = node.Container()
}
}
}
return matches
}
func findAll(prefix, findKey, findValue string, container Container) map[string]Container {
if container == nil {
return nil
}
if v, err := container.Get(findKey); err == nil && v != nil {
if vs, ok := v.Value().(string); ok && vs == findValue {
return map[string]Container{
prefix: container,
}
}
}
matches := map[string]Container{}
switch it := container.(type) {
case *nodeMap:
for k, v := range *it {
for route, match := range findAll(fmt.Sprintf("%s/%s", prefix, k), findKey, findValue, v.Container()) {
matches[route] = match
}
}
case *nodeSlice:
for i, v := range *it {
for route, match := range findAll(fmt.Sprintf("%s/%d", prefix, i), findKey, findValue, v.Container()) {
matches[route] = match
}
}
}
return matches
}

View File

@@ -0,0 +1,74 @@
package yamlpatch_test
import (
yamlpatch "github.com/krishicks/yaml-patch"
yaml "gopkg.in/yaml.v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
var _ = Describe("Pathfinder", func() {
var pathfinder *yamlpatch.PathFinder
BeforeEach(func() {
var iface interface{}
bs := []byte(`
jobs:
- name: job1
plan:
- get: A
args:
- arg: arg1
- arg: arg2
bool: true
- get: B
- get: C/D
- name: job2
plan:
- aggregate:
- get: C
- get: A
`)
err := yaml.Unmarshal(bs, &iface)
Expect(err).NotTo(HaveOccurred())
container := yamlpatch.NewNode(&iface).Container()
pathfinder = yamlpatch.NewPathFinder(container)
})
Describe("Find", func() {
DescribeTable(
"should",
func(path string, expected []string) {
actual := pathfinder.Find(path)
Expect(actual).To(HaveLen(len(expected)))
for _, el := range expected {
Expect(actual).To(ContainElement(el))
}
},
Entry("return a route for the root object", "/", []string{"/"}),
Entry("return a route for an object under the root", "/jobs", []string{"/jobs"}),
Entry("return a route for an element within an object under the root", "/jobs/0", []string{"/jobs/0"}),
Entry("return a route for an object within an element within an object under the root", "/jobs/0/plan", []string{"/jobs/0/plan"}),
Entry("return a route for an object within an element within an object under the root", "/jobs/0/plan/1", []string{"/jobs/0/plan/1"}),
Entry("return routes for multiple matches", "/jobs/get=A", []string{"/jobs/0/plan/0", "/jobs/1/plan/0/aggregate/1"}),
Entry("return a route for a single submatch with help", "/jobs/get=A/args/arg=arg2", []string{"/jobs/0/plan/0/args/1"}),
Entry("return a route for a single submatch with no help", "/jobs/get=A/arg=arg2", []string{"/jobs/0/plan/0/args/1"}),
Entry("return a route for a single submatch with help using escape ordering", "/jobs/get=C~1D", []string{"/jobs/0/plan/2"}),
Entry("return a route when given a pointer with a leaf that does not exist", "/jobs/name=job1/nonexistent", []string{"/jobs/0/nonexistent"}),
Entry("return a route when given a pointer with an array thingy", "/jobs/name=job1/plan/-", []string{"/jobs/0/plan/-"}),
)
DescribeTable(
"should not",
func(path string) {
Expect(pathfinder.Find(path)).To(BeNil())
},
Entry("return any routes when given a bad index", "/jobs/2"),
Entry("return any routes when given a bad index", "/jobs/-1"),
)
})
})

View File

@@ -0,0 +1,51 @@
package yamlpatch
import (
"fmt"
"regexp"
)
// PlaceholderWrapper can be used to wrap placeholders that make YAML invalid
// in single quotes to make otherwise valid YAML
type PlaceholderWrapper struct {
LeftSide string
RightSide string
unwrappedRegex *regexp.Regexp
wrappedRegex *regexp.Regexp
}
// NewPlaceholderWrapper returns a new PlaceholderWrapper which knows how to
// wrap and unwrap the provided left and right sides of a placeholder, e.g. {{
// and }}
func NewPlaceholderWrapper(left, right string) *PlaceholderWrapper {
escapedLeft := regexp.QuoteMeta(left)
escapedRight := regexp.QuoteMeta(right)
unwrappedRegex := regexp.MustCompile(`\s` + escapedLeft + `([^` + escapedRight + `]+)` + escapedRight)
wrappedRegex := regexp.MustCompile(`\s'` + escapedLeft + `([^` + escapedRight + `]+)` + escapedRight + `'`)
return &PlaceholderWrapper{
LeftSide: left,
RightSide: right,
unwrappedRegex: unwrappedRegex,
wrappedRegex: wrappedRegex,
}
}
// Wrap the placeholder in single quotes to make it valid YAML
func (w *PlaceholderWrapper) Wrap(input []byte) []byte {
if !w.unwrappedRegex.Match(input) {
return input
}
return w.unwrappedRegex.ReplaceAll(input, []byte(fmt.Sprintf(` '%s$1%s'`, w.LeftSide, w.RightSide)))
}
// Unwrap the single quotes from the placeholder to make it invalid YAML
// (again)
func (w *PlaceholderWrapper) Unwrap(input []byte) []byte {
if !w.wrappedRegex.Match(input) {
return input
}
return w.wrappedRegex.ReplaceAll(input, []byte(fmt.Sprintf(` %s$1%s`, w.LeftSide, w.RightSide)))
}

View File

@@ -0,0 +1,93 @@
package yamlpatch_test
import (
yamlpatch "github.com/krishicks/yaml-patch"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("PlaceholderWrapper", func() {
var placeholderWrapper *yamlpatch.PlaceholderWrapper
BeforeEach(func() {
placeholderWrapper = yamlpatch.NewPlaceholderWrapper("{{", "}}")
})
Describe("Wrap", func() {
It("returns the original content", func() {
input := []byte(`content without any placeholders`)
actual := placeholderWrapper.Wrap(input)
Expect(actual).To(Equal(input))
})
It("returns the content with the placeholder wrapped when the content contains a placeholder", func() {
input := []byte(`content with a {{placeholder}}`)
expected := []byte(`content with a '{{placeholder}}'`)
actual := placeholderWrapper.Wrap(input)
Expect(actual).To(Equal(expected))
})
It("returns the content with the placeholder wrapped when the content contains a line with only a placeholder", func() {
input := []byte(`
content: |
{{placeholder}}
`)
expected := []byte(`
content: |
'{{placeholder}}'
`)
actual := placeholderWrapper.Wrap(input)
Expect(actual).To(Equal(expected))
})
It("returns the original content when the content contains an already-wrapped placeholder", func() {
input := []byte(`content with a wrapped '{{placeholder}}'`)
actual := placeholderWrapper.Wrap(input)
Expect(string(actual)).To(Equal(string(input)))
})
It("supports alternate placeholders", func() {
placeholderWrapper = yamlpatch.NewPlaceholderWrapper("((", "))")
input := []byte(`content with an ((alternate-placeholder))`)
expected := []byte(`content with an '((alternate-placeholder))'`)
actual := placeholderWrapper.Wrap(input)
Expect(actual).To(Equal(expected))
})
})
Describe("Unwrap", func() {
It("returns the original content", func() {
input := []byte(`content without any placeholders`)
actual := placeholderWrapper.Unwrap(input)
Expect(string(actual)).To(Equal(string(input)))
})
It("returns the content with the placeholder unwrapped when the content contains a wrapped placeholder", func() {
input := []byte(`content with a '{{placeholder}}'`)
expected := []byte(`content with a {{placeholder}}`)
actual := placeholderWrapper.Unwrap(input)
Expect(string(actual)).To(Equal(string(expected)))
})
It("returns the content with the placeholder unwrapped when the content contains a line with only a wrapped placeholder", func() {
input := []byte(`
content: |
'{{placeholder}}'
`)
expected := []byte(`
content: |
{{placeholder}}
`)
actual := placeholderWrapper.Unwrap(input)
Expect(string(actual)).To(Equal(string(expected)))
})
It("supports alternate placeholders", func() {
placeholderWrapper = yamlpatch.NewPlaceholderWrapper("((", "))")
input := []byte(`content with an '((alternate-placeholder))'`)
expected := []byte(`content with an ((alternate-placeholder))`)
actual := placeholderWrapper.Unwrap(input)
Expect(actual).To(Equal(expected))
})
})
})

View File

@@ -0,0 +1,13 @@
package yamlpatch_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestYamlPatch(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "YamlPatch Suite")
}