use istio client-go library instead of knative (#1661)

use istio client-go library instead of knative
bump kubernetes dependency version
change code coverage to codecov
This commit is contained in:
zryfish
2019-12-13 11:26:18 +08:00
committed by GitHub
parent f249a6e081
commit ea88c8803d
2071 changed files with 354531 additions and 108336 deletions

201
vendor/sigs.k8s.io/structured-merge-diff/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.

View File

@@ -0,0 +1,21 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
// Package fieldpath defines a way for referencing path elements (e.g., an
// index in an array, or a key in a map). It provides types for arranging these
// into paths for referencing nested fields, and for grouping those into sets,
// for referencing multiple nested fields.
package fieldpath

View File

@@ -0,0 +1,275 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package fieldpath
import (
"fmt"
"sort"
"strings"
"sigs.k8s.io/structured-merge-diff/value"
)
// PathElement describes how to select a child field given a containing object.
type PathElement struct {
// Exactly one of the following fields should be non-nil.
// FieldName selects a single field from a map (reminder: this is also
// how structs are represented). The containing object must be a map.
FieldName *string
// Key selects the list element which has fields matching those given.
// The containing object must be an associative list with map typed
// elements.
Key *value.Map
// Value selects the list element with the given value. The containing
// object must be an associative list with a primitive typed element
// (i.e., a set).
Value *value.Value
// Index selects a list element by its index number. The containing
// object must be an atomic list.
Index *int
}
// Less provides an order for path elements.
func (e PathElement) Less(rhs PathElement) bool {
if e.FieldName != nil {
if rhs.FieldName == nil {
return true
}
return *e.FieldName < *rhs.FieldName
} else if rhs.FieldName != nil {
return false
}
if e.Key != nil {
if rhs.Key == nil {
return true
}
return e.Key.Less(rhs.Key)
} else if rhs.Key != nil {
return false
}
if e.Value != nil {
if rhs.Value == nil {
return true
}
return e.Value.Less(*rhs.Value)
} else if rhs.Value != nil {
return false
}
if e.Index != nil {
if rhs.Index == nil {
return true
}
return *e.Index < *rhs.Index
} else if rhs.Index != nil {
// Yes, I know the next statement is the same. But this way
// the obvious way of extending the function wil be bug-free.
return false
}
return false
}
// Equals returns true if both path elements are equal.
func (e PathElement) Equals(rhs PathElement) bool {
return !e.Less(rhs) && !rhs.Less(e)
}
// String presents the path element as a human-readable string.
func (e PathElement) String() string {
switch {
case e.FieldName != nil:
return "." + *e.FieldName
case e.Key != nil:
strs := make([]string, len(e.Key.Items))
for i, k := range e.Key.Items {
strs[i] = fmt.Sprintf("%v=%v", k.Name, k.Value)
}
// The order must be canonical, since we use the string value
// in a set structure.
sort.Strings(strs)
return "[" + strings.Join(strs, ",") + "]"
case e.Value != nil:
return fmt.Sprintf("[=%v]", e.Value)
case e.Index != nil:
return fmt.Sprintf("[%v]", *e.Index)
default:
return "{{invalid path element}}"
}
}
// KeyByFields is a helper function which constructs a key for an associative
// list type. `nameValues` must have an even number of entries, alternating
// names (type must be string) with values (type must be value.Value). If these
// conditions are not met, KeyByFields will panic--it's intended for static
// construction and shouldn't have user-produced values passed to it.
func KeyByFields(nameValues ...interface{}) []value.Field {
if len(nameValues)%2 != 0 {
panic("must have a value for every name")
}
out := []value.Field{}
for i := 0; i < len(nameValues)-1; i += 2 {
out = append(out, value.Field{
Name: nameValues[i].(string),
Value: nameValues[i+1].(value.Value),
})
}
return out
}
// PathElementSet is a set of path elements.
// TODO: serialize as a list.
type PathElementSet struct {
members sortedPathElements
}
type sortedPathElements []PathElement
// Implement the sort interface; this would permit bulk creation, which would
// be faster than doing it one at a time via Insert.
func (spe sortedPathElements) Len() int { return len(spe) }
func (spe sortedPathElements) Less(i, j int) bool { return spe[i].Less(spe[j]) }
func (spe sortedPathElements) Swap(i, j int) { spe[i], spe[j] = spe[j], spe[i] }
// Insert adds pe to the set.
func (s *PathElementSet) Insert(pe PathElement) {
loc := sort.Search(len(s.members), func(i int) bool {
return !s.members[i].Less(pe)
})
if loc == len(s.members) {
s.members = append(s.members, pe)
return
}
if s.members[loc].Equals(pe) {
return
}
s.members = append(s.members, PathElement{})
copy(s.members[loc+1:], s.members[loc:])
s.members[loc] = pe
}
// Union returns a set containing elements that appear in either s or s2.
func (s *PathElementSet) Union(s2 *PathElementSet) *PathElementSet {
out := &PathElementSet{}
i, j := 0, 0
for i < len(s.members) && j < len(s2.members) {
if s.members[i].Less(s2.members[j]) {
out.members = append(out.members, s.members[i])
i++
} else {
out.members = append(out.members, s2.members[j])
if !s2.members[j].Less(s.members[i]) {
i++
}
j++
}
}
if i < len(s.members) {
out.members = append(out.members, s.members[i:]...)
}
if j < len(s2.members) {
out.members = append(out.members, s2.members[j:]...)
}
return out
}
// Intersection returns a set containing elements which appear in both s and s2.
func (s *PathElementSet) Intersection(s2 *PathElementSet) *PathElementSet {
out := &PathElementSet{}
i, j := 0, 0
for i < len(s.members) && j < len(s2.members) {
if s.members[i].Less(s2.members[j]) {
i++
} else {
if !s2.members[j].Less(s.members[i]) {
out.members = append(out.members, s.members[i])
i++
}
j++
}
}
return out
}
// Difference returns a set containing elements which appear in s but not in s2.
func (s *PathElementSet) Difference(s2 *PathElementSet) *PathElementSet {
out := &PathElementSet{}
i, j := 0, 0
for i < len(s.members) && j < len(s2.members) {
if s.members[i].Less(s2.members[j]) {
out.members = append(out.members, s.members[i])
i++
} else {
if !s2.members[j].Less(s.members[i]) {
i++
}
j++
}
}
if i < len(s.members) {
out.members = append(out.members, s.members[i:]...)
}
return out
}
// Size retuns the number of elements in the set.
func (s *PathElementSet) Size() int { return len(s.members) }
// Has returns true if pe is a member of the set.
func (s *PathElementSet) Has(pe PathElement) bool {
loc := sort.Search(len(s.members), func(i int) bool {
return !s.members[i].Less(pe)
})
if loc == len(s.members) {
return false
}
if s.members[loc].Equals(pe) {
return true
}
return false
}
// Equals returns true if s and s2 have exactly the same members.
func (s *PathElementSet) Equals(s2 *PathElementSet) bool {
if len(s.members) != len(s2.members) {
return false
}
for k := range s.members {
if !s.members[k].Equals(s2.members[k]) {
return false
}
}
return true
}
// Iterate calls f for each PathElement in the set. The order is deterministic.
func (s *PathElementSet) Iterate(f func(PathElement)) {
for _, pe := range s.members {
f(pe)
}
}

View File

@@ -0,0 +1,123 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package fieldpath
import (
"sigs.k8s.io/structured-merge-diff/value"
)
// SetFromValue creates a set containing every leaf field mentioned in v.
func SetFromValue(v value.Value) *Set {
s := NewSet()
w := objectWalker{
path: Path{},
value: v,
do: func(p Path) { s.Insert(p) },
}
w.walk()
return s
}
type objectWalker struct {
path Path
value value.Value
do func(Path)
}
func (w *objectWalker) walk() {
switch {
case w.value.Null:
case w.value.FloatValue != nil:
case w.value.IntValue != nil:
case w.value.StringValue != nil:
case w.value.BooleanValue != nil:
// All leaf fields handled the same way (after the switch
// statement).
// Descend
case w.value.ListValue != nil:
// If the list were atomic, we'd break here, but we don't have
// a schema, so we can't tell.
for i, child := range w.value.ListValue.Items {
w2 := *w
w2.path = append(w.path, GuessBestListPathElement(i, child))
w2.value = child
w2.walk()
}
return
case w.value.MapValue != nil:
// If the map/struct were atomic, we'd break here, but we don't
// have a schema, so we can't tell.
for i := range w.value.MapValue.Items {
child := w.value.MapValue.Items[i]
w2 := *w
w2.path = append(w.path, PathElement{FieldName: &child.Name})
w2.value = child.Value
w2.walk()
}
return
}
// Leaf fields get added to the set.
if len(w.path) > 0 {
w.do(w.path)
}
}
// AssociativeListCandidateFieldNames lists the field names which are
// considered keys if found in a list element.
var AssociativeListCandidateFieldNames = []string{
"key",
"id",
"name",
}
// GuessBestListPathElement guesses whether item is an associative list
// element, which should be referenced by key(s), or if it is not and therefore
// referencing by index is acceptable. Currently this is done by checking
// whether item has any of the fields listed in
// AssociativeListCandidateFieldNames which have scalar values.
func GuessBestListPathElement(index int, item value.Value) PathElement {
if item.MapValue == nil {
// Non map items could be parts of sets or regular "atomic"
// lists. We won't try to guess whether something should be a
// set or not.
return PathElement{Index: &index}
}
var keys []value.Field
for _, name := range AssociativeListCandidateFieldNames {
f, ok := item.MapValue.Get(name)
if !ok {
continue
}
// only accept primitive/scalar types as keys.
if f.Value.Null || f.Value.MapValue != nil || f.Value.ListValue != nil {
continue
}
keys = append(keys, *f)
}
if len(keys) > 0 {
return PathElement{Key: &value.Map{Items: keys}}
}
return PathElement{Index: &index}
}

View File

@@ -0,0 +1,97 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package fieldpath
// APIVersion describes the version of an object or of a fieldset.
type APIVersion string
type VersionedSet interface {
Set() *Set
APIVersion() APIVersion
Applied() bool
}
// VersionedSet associates a version to a set.
type versionedSet struct {
set *Set
apiVersion APIVersion
applied bool
}
func NewVersionedSet(set *Set, apiVersion APIVersion, applied bool) VersionedSet {
return versionedSet{
set: set,
apiVersion: apiVersion,
applied: applied,
}
}
func (v versionedSet) Set() *Set {
return v.set
}
func (v versionedSet) APIVersion() APIVersion {
return v.apiVersion
}
func (v versionedSet) Applied() bool {
return v.applied
}
// ManagedFields is a map from manager to VersionedSet (what they own in
// what version).
type ManagedFields map[string]VersionedSet
// Difference returns a symmetric difference between two Managers. If a
// given user's entry has version X in lhs and version Y in rhs, then
// the return value for that user will be from rhs. If the difference for
// a user is an empty set, that user will not be inserted in the map.
func (lhs ManagedFields) Difference(rhs ManagedFields) ManagedFields {
diff := ManagedFields{}
for manager, left := range lhs {
right, ok := rhs[manager]
if !ok {
if !left.Set().Empty() {
diff[manager] = left
}
continue
}
// If we have sets in both but their version
// differs, we don't even diff and keep the
// entire thing.
if left.APIVersion() != right.APIVersion() {
diff[manager] = right
continue
}
newSet := left.Set().Difference(right.Set()).Union(right.Set().Difference(left.Set()))
if !newSet.Empty() {
diff[manager] = NewVersionedSet(newSet, right.APIVersion(), false)
}
}
for manager, set := range rhs {
if _, ok := lhs[manager]; ok {
// Already done
continue
}
if !set.Set().Empty() {
diff[manager] = set
}
}
return diff
}

View File

@@ -0,0 +1,115 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package fieldpath
import (
"fmt"
"strings"
"sigs.k8s.io/structured-merge-diff/value"
)
// Path describes how to select a potentially deeply-nested child field given a
// containing object.
type Path []PathElement
func (fp Path) String() string {
strs := make([]string, len(fp))
for i := range fp {
strs[i] = fp[i].String()
}
return strings.Join(strs, "")
}
// Equals returns true if the two paths are equivalent.
func (fp Path) Equals(fp2 Path) bool {
return !fp.Less(fp2) && !fp2.Less(fp)
}
// Less provides a lexical order for Paths.
func (fp Path) Less(rhs Path) bool {
i := 0
for {
if i >= len(fp) && i >= len(rhs) {
// Paths are the same length and all items are equal.
return false
}
if i >= len(fp) {
// LHS is shorter.
return true
}
if i >= len(rhs) {
// RHS is shorter.
return false
}
if fp[i].Less(rhs[i]) {
// LHS is less; return
return true
}
if rhs[i].Less(fp[i]) {
// RHS is less; return
return false
}
// The items are equal; continue.
i++
}
}
func (fp Path) Copy() Path {
new := make(Path, len(fp))
copy(new, fp)
return new
}
// MakePath constructs a Path. The parts may be PathElements, ints, strings.
func MakePath(parts ...interface{}) (Path, error) {
var fp Path
for _, p := range parts {
switch t := p.(type) {
case PathElement:
fp = append(fp, t)
case int:
// TODO: Understand schema and object and convert this to the
// FieldSpecifier below if appropriate.
fp = append(fp, PathElement{Index: &t})
case string:
fp = append(fp, PathElement{FieldName: &t})
case []value.Field:
if len(t) == 0 {
return nil, fmt.Errorf("associative list key type path elements must have at least one key (got zero)")
}
fp = append(fp, PathElement{Key: &value.Map{Items: t}})
case value.Value:
// TODO: understand schema and verify that this is a set type
// TODO: make a copy of t
fp = append(fp, PathElement{Value: &t})
default:
return nil, fmt.Errorf("unable to make %#v into a path element", p)
}
}
return fp, nil
}
// MakePathOrDie panics if parts can't be turned into a path. Good for things
// that are known at complie time.
func MakePathOrDie(parts ...interface{}) Path {
fp, err := MakePath(parts...)
if err != nil {
panic(err)
}
return fp
}

View File

@@ -0,0 +1,155 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package fieldpath
import (
"errors"
"fmt"
"io"
"strconv"
"strings"
jsoniter "github.com/json-iterator/go"
"sigs.k8s.io/structured-merge-diff/value"
)
var ErrUnknownPathElementType = errors.New("unknown path element type")
const (
// Field indicates that the content of this path element is a field's name
peField = "f"
// Value indicates that the content of this path element is a field's value
peValue = "v"
// Index indicates that the content of this path element is an index in an array
peIndex = "i"
// Key indicates that the content of this path element is a key value map
peKey = "k"
// Separator separates the type of a path element from the contents
peSeparator = ":"
)
var (
peFieldSepBytes = []byte(peField + peSeparator)
peValueSepBytes = []byte(peValue + peSeparator)
peIndexSepBytes = []byte(peIndex + peSeparator)
peKeySepBytes = []byte(peKey + peSeparator)
peSepBytes = []byte(peSeparator)
)
// DeserializePathElement parses a serialized path element
func DeserializePathElement(s string) (PathElement, error) {
b := []byte(s)
if len(b) < 2 {
return PathElement{}, errors.New("key must be 2 characters long:")
}
typeSep, b := b[:2], b[2:]
if typeSep[1] != peSepBytes[0] {
return PathElement{}, fmt.Errorf("missing colon: %v", s)
}
switch typeSep[0] {
case peFieldSepBytes[0]:
// Slice s rather than convert b, to save on
// allocations.
str := s[2:]
return PathElement{
FieldName: &str,
}, nil
case peValueSepBytes[0]:
iter := readPool.BorrowIterator(b)
defer readPool.ReturnIterator(iter)
v, err := value.ReadJSONIter(iter)
if err != nil {
return PathElement{}, err
}
return PathElement{Value: &v}, nil
case peKeySepBytes[0]:
iter := readPool.BorrowIterator(b)
defer readPool.ReturnIterator(iter)
v, err := value.ReadJSONIter(iter)
if err != nil {
return PathElement{}, err
}
if v.MapValue == nil {
return PathElement{}, fmt.Errorf("expected key value pairs but got %#v", v)
}
return PathElement{Key: v.MapValue}, nil
case peIndexSepBytes[0]:
i, err := strconv.Atoi(s[2:])
if err != nil {
return PathElement{}, err
}
return PathElement{
Index: &i,
}, nil
default:
return PathElement{}, ErrUnknownPathElementType
}
}
var (
readPool = jsoniter.NewIterator(jsoniter.ConfigCompatibleWithStandardLibrary).Pool()
writePool = jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, nil, 1024).Pool()
)
// SerializePathElement serializes a path element
func SerializePathElement(pe PathElement) (string, error) {
buf := strings.Builder{}
err := serializePathElementToWriter(&buf, pe)
return buf.String(), err
}
func serializePathElementToWriter(w io.Writer, pe PathElement) error {
stream := writePool.BorrowStream(w)
defer writePool.ReturnStream(stream)
switch {
case pe.FieldName != nil:
if _, err := stream.Write(peFieldSepBytes); err != nil {
return err
}
stream.WriteRaw(*pe.FieldName)
case pe.Key != nil:
if _, err := stream.Write(peKeySepBytes); err != nil {
return err
}
v := value.Value{MapValue: pe.Key}
v.WriteJSONStream(stream)
case pe.Value != nil:
if _, err := stream.Write(peValueSepBytes); err != nil {
return err
}
pe.Value.WriteJSONStream(stream)
case pe.Index != nil:
if _, err := stream.Write(peIndexSepBytes); err != nil {
return err
}
stream.WriteInt(*pe.Index)
default:
return errors.New("invalid PathElement")
}
b := stream.Buffer()
err := stream.Flush()
// Help jsoniter manage its buffers--without this, the next
// use of the stream is likely to require an allocation. Look
// at the jsoniter stream code to understand why. They were probably
// optimizing for folks using the buffer directly.
stream.SetBuffer(b[:0])
return err
}

View File

@@ -0,0 +1,237 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package fieldpath
import (
"bytes"
"io"
"unsafe"
jsoniter "github.com/json-iterator/go"
)
func (s *Set) ToJSON() ([]byte, error) {
buf := bytes.Buffer{}
err := s.ToJSONStream(&buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *Set) ToJSONStream(w io.Writer) error {
stream := writePool.BorrowStream(w)
defer writePool.ReturnStream(stream)
var r reusableBuilder
stream.WriteObjectStart()
err := s.emitContents_v1(false, stream, &r)
if err != nil {
return err
}
stream.WriteObjectEnd()
return stream.Flush()
}
func manageMemory(stream *jsoniter.Stream) error {
// Help jsoniter manage its buffers--without this, it does a bunch of
// alloctaions that are not necessary. They were probably optimizing
// for folks using the buffer directly.
b := stream.Buffer()
if len(b) > 4096 || cap(b)-len(b) < 2048 {
if err := stream.Flush(); err != nil {
return err
}
stream.SetBuffer(b[:0])
}
return nil
}
type reusableBuilder struct {
bytes.Buffer
}
func (r *reusableBuilder) unsafeString() string {
b := r.Bytes()
return *(*string)(unsafe.Pointer(&b))
}
func (r *reusableBuilder) reset() *bytes.Buffer {
r.Reset()
return &r.Buffer
}
func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream, r *reusableBuilder) error {
mi, ci := 0, 0
first := true
preWrite := func() {
if first {
first = false
return
}
stream.WriteMore()
}
for mi < len(s.Members.members) && ci < len(s.Children.members) {
mpe := s.Members.members[mi]
cpe := s.Children.members[ci].pathElement
if mpe.Less(cpe) {
preWrite()
if err := serializePathElementToWriter(r.reset(), mpe); err != nil {
return err
}
stream.WriteObjectField(r.unsafeString())
stream.WriteEmptyObject()
mi++
} else if cpe.Less(mpe) {
preWrite()
if err := serializePathElementToWriter(r.reset(), cpe); err != nil {
return err
}
stream.WriteObjectField(r.unsafeString())
stream.WriteObjectStart()
if err := s.Children.members[ci].set.emitContents_v1(false, stream, r); err != nil {
return err
}
stream.WriteObjectEnd()
ci++
} else {
preWrite()
if err := serializePathElementToWriter(r.reset(), cpe); err != nil {
return err
}
stream.WriteObjectField(r.unsafeString())
stream.WriteObjectStart()
if err := s.Children.members[ci].set.emitContents_v1(true, stream, r); err != nil {
return err
}
stream.WriteObjectEnd()
mi++
ci++
}
}
for mi < len(s.Members.members) {
mpe := s.Members.members[mi]
preWrite()
if err := serializePathElementToWriter(r.reset(), mpe); err != nil {
return err
}
stream.WriteObjectField(r.unsafeString())
stream.WriteEmptyObject()
mi++
}
for ci < len(s.Children.members) {
cpe := s.Children.members[ci].pathElement
preWrite()
if err := serializePathElementToWriter(r.reset(), cpe); err != nil {
return err
}
stream.WriteObjectField(r.unsafeString())
stream.WriteObjectStart()
if err := s.Children.members[ci].set.emitContents_v1(false, stream, r); err != nil {
return err
}
stream.WriteObjectEnd()
ci++
}
if includeSelf && !first {
preWrite()
stream.WriteObjectField(".")
stream.WriteEmptyObject()
}
return manageMemory(stream)
}
// FromJSON clears s and reads a JSON formatted set structure.
func (s *Set) FromJSON(r io.Reader) error {
// The iterator pool is completely useless for memory management, grrr.
iter := jsoniter.Parse(jsoniter.ConfigCompatibleWithStandardLibrary, r, 4096)
found, _ := readIter_v1(iter)
if found == nil {
*s = Set{}
} else {
*s = *found
}
return iter.Error
}
// returns true if this subtree is also (or only) a member of parent; s is nil
// if there are no further children.
func readIter_v1(iter *jsoniter.Iterator) (children *Set, isMember bool) {
iter.ReadMapCB(func(iter *jsoniter.Iterator, key string) bool {
if key == "." {
isMember = true
iter.Skip()
return true
}
pe, err := DeserializePathElement(key)
if err == ErrUnknownPathElementType {
// Ignore these-- a future version maybe knows what
// they are. We drop these completely rather than try
// to preserve things we don't understand.
iter.Skip()
return true
} else if err != nil {
iter.ReportError("parsing key as path element", err.Error())
iter.Skip()
return true
}
grandchildren, childIsMember := readIter_v1(iter)
if childIsMember {
if children == nil {
children = &Set{}
}
m := &children.Members.members
// Since we expect that most of the time these will have been
// serialized in the right order, we just verify that and append.
appendOK := len(*m) == 0 || (*m)[len(*m)-1].Less(pe)
if appendOK {
*m = append(*m, pe)
} else {
children.Members.Insert(pe)
}
}
if grandchildren != nil {
if children == nil {
children = &Set{}
}
// Since we expect that most of the time these will have been
// serialized in the right order, we just verify that and append.
m := &children.Children.members
appendOK := len(*m) == 0 || (*m)[len(*m)-1].pathElement.Less(pe)
if appendOK {
*m = append(*m, setNode{pe, grandchildren})
} else {
*children.Children.Descend(pe) = *grandchildren
}
}
return true
})
if children == nil {
isMember = true
}
return children, isMember
}

View File

@@ -0,0 +1,348 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package fieldpath
import (
"sort"
"strings"
)
// Set identifies a set of fields.
type Set struct {
// Members lists fields that are part of the set.
// TODO: will be serialized as a list of path elements.
Members PathElementSet
// Children lists child fields which themselves have children that are
// members of the set. Appearance in this list does not imply membership.
// Note: this is a tree, not an arbitrary graph.
Children SetNodeMap
}
// NewSet makes a set from a list of paths.
func NewSet(paths ...Path) *Set {
s := &Set{}
for _, p := range paths {
s.Insert(p)
}
return s
}
// Insert adds the field identified by `p` to the set. Important: parent fields
// are NOT added to the set; if that is desired, they must be added separately.
func (s *Set) Insert(p Path) {
if len(p) == 0 {
// Zero-length path identifies the entire object; we don't
// track top-level ownership.
return
}
for {
if len(p) == 1 {
s.Members.Insert(p[0])
return
}
s = s.Children.Descend(p[0])
p = p[1:]
}
}
// Union returns a Set containing elements which appear in either s or s2.
func (s *Set) Union(s2 *Set) *Set {
return &Set{
Members: *s.Members.Union(&s2.Members),
Children: *s.Children.Union(&s2.Children),
}
}
// Intersection returns a Set containing leaf elements which appear in both s
// and s2. Intersection can be constructed from Union and Difference operations
// (example in the tests) but it's much faster to do it in one pass.
func (s *Set) Intersection(s2 *Set) *Set {
return &Set{
Members: *s.Members.Intersection(&s2.Members),
Children: *s.Children.Intersection(&s2.Children),
}
}
// Difference returns a Set containing elements which:
// * appear in s
// * do not appear in s2
//
// In other words, for leaf fields, this acts like a regular set difference
// operation. When non leaf fields are compared with leaf fields ("parents"
// which contain "children"), the effect is:
// * parent - child = parent
// * child - parent = {empty set}
func (s *Set) Difference(s2 *Set) *Set {
return &Set{
Members: *s.Members.Difference(&s2.Members),
Children: *s.Children.Difference(s2),
}
}
// Size returns the number of members of the set.
func (s *Set) Size() int {
return s.Members.Size() + s.Children.Size()
}
// Empty returns true if there are no members of the set. It is a separate
// function from Size since it's common to check whether size > 0, and
// potentially much faster to return as soon as a single element is found.
func (s *Set) Empty() bool {
if s.Members.Size() > 0 {
return false
}
return s.Children.Empty()
}
// Has returns true if the field referenced by `p` is a member of the set.
func (s *Set) Has(p Path) bool {
if len(p) == 0 {
// No one owns "the entire object"
return false
}
for {
if len(p) == 1 {
return s.Members.Has(p[0])
}
var ok bool
s, ok = s.Children.Get(p[0])
if !ok {
return false
}
p = p[1:]
}
}
// Equals returns true if s and s2 have exactly the same members.
func (s *Set) Equals(s2 *Set) bool {
return s.Members.Equals(&s2.Members) && s.Children.Equals(&s2.Children)
}
// String returns the set one element per line.
func (s *Set) String() string {
elements := []string{}
s.Iterate(func(p Path) {
elements = append(elements, p.String())
})
return strings.Join(elements, "\n")
}
// Iterate calls f once for each field that is a member of the set (preorder
// DFS). The path passed to f will be reused so make a copy if you wish to keep
// it.
func (s *Set) Iterate(f func(Path)) {
s.iteratePrefix(Path{}, f)
}
func (s *Set) iteratePrefix(prefix Path, f func(Path)) {
s.Members.Iterate(func(pe PathElement) { f(append(prefix, pe)) })
s.Children.iteratePrefix(prefix, f)
}
// WithPrefix returns the subset of paths which begin with the given prefix,
// with the prefix not included.
func (s *Set) WithPrefix(pe PathElement) *Set {
subset, ok := s.Children.Get(pe)
if !ok {
return NewSet()
}
return subset
}
// setNode is a pair of PathElement / Set, for the purpose of expressing
// nested set membership.
type setNode struct {
pathElement PathElement
set *Set
}
// SetNodeMap is a map of PathElement to subset.
type SetNodeMap struct {
members sortedSetNode
}
type sortedSetNode []setNode
// Implement the sort interface; this would permit bulk creation, which would
// be faster than doing it one at a time via Insert.
func (s sortedSetNode) Len() int { return len(s) }
func (s sortedSetNode) Less(i, j int) bool { return s[i].pathElement.Less(s[j].pathElement) }
func (s sortedSetNode) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// Descend adds pe to the set if necessary, returning the associated subset.
func (s *SetNodeMap) Descend(pe PathElement) *Set {
loc := sort.Search(len(s.members), func(i int) bool {
return !s.members[i].pathElement.Less(pe)
})
if loc == len(s.members) {
s.members = append(s.members, setNode{pathElement: pe, set: &Set{}})
return s.members[loc].set
}
if s.members[loc].pathElement.Equals(pe) {
return s.members[loc].set
}
s.members = append(s.members, setNode{})
copy(s.members[loc+1:], s.members[loc:])
s.members[loc] = setNode{pathElement: pe, set: &Set{}}
return s.members[loc].set
}
// Size returns the sum of the number of members of all subsets.
func (s *SetNodeMap) Size() int {
count := 0
for _, v := range s.members {
count += v.set.Size()
}
return count
}
// Empty returns false if there's at least one member in some child set.
func (s *SetNodeMap) Empty() bool {
for _, n := range s.members {
if !n.set.Empty() {
return false
}
}
return true
}
// Get returns (the associated set, true) or (nil, false) if there is none.
func (s *SetNodeMap) Get(pe PathElement) (*Set, bool) {
loc := sort.Search(len(s.members), func(i int) bool {
return !s.members[i].pathElement.Less(pe)
})
if loc == len(s.members) {
return nil, false
}
if s.members[loc].pathElement.Equals(pe) {
return s.members[loc].set, true
}
return nil, false
}
// Equals returns true if s and s2 have the same structure (same nested
// child sets).
func (s *SetNodeMap) Equals(s2 *SetNodeMap) bool {
if len(s.members) != len(s2.members) {
return false
}
for i := range s.members {
if !s.members[i].pathElement.Equals(s2.members[i].pathElement) {
return false
}
if !s.members[i].set.Equals(s2.members[i].set) {
return false
}
}
return true
}
// Union returns a SetNodeMap with members that appear in either s or s2.
func (s *SetNodeMap) Union(s2 *SetNodeMap) *SetNodeMap {
out := &SetNodeMap{}
i, j := 0, 0
for i < len(s.members) && j < len(s2.members) {
if s.members[i].pathElement.Less(s2.members[j].pathElement) {
out.members = append(out.members, s.members[i])
i++
} else {
if !s2.members[j].pathElement.Less(s.members[i].pathElement) {
out.members = append(out.members, setNode{pathElement: s.members[i].pathElement, set: s.members[i].set.Union(s2.members[j].set)})
i++
} else {
out.members = append(out.members, s2.members[j])
}
j++
}
}
if i < len(s.members) {
out.members = append(out.members, s.members[i:]...)
}
if j < len(s2.members) {
out.members = append(out.members, s2.members[j:]...)
}
return out
}
// Intersection returns a SetNodeMap with members that appear in both s and s2.
func (s *SetNodeMap) Intersection(s2 *SetNodeMap) *SetNodeMap {
out := &SetNodeMap{}
i, j := 0, 0
for i < len(s.members) && j < len(s2.members) {
if s.members[i].pathElement.Less(s2.members[j].pathElement) {
i++
} else {
if !s2.members[j].pathElement.Less(s.members[i].pathElement) {
res := s.members[i].set.Intersection(s2.members[j].set)
if !res.Empty() {
out.members = append(out.members, setNode{pathElement: s.members[i].pathElement, set: res})
}
i++
}
j++
}
}
return out
}
// Difference returns a SetNodeMap with members that appear in s but not in s2.
func (s *SetNodeMap) Difference(s2 *Set) *SetNodeMap {
out := &SetNodeMap{}
i, j := 0, 0
for i < len(s.members) && j < len(s2.Children.members) {
if s.members[i].pathElement.Less(s2.Children.members[j].pathElement) {
out.members = append(out.members, setNode{pathElement: s.members[i].pathElement, set: s.members[i].set})
i++
} else {
if !s2.Children.members[j].pathElement.Less(s.members[i].pathElement) {
diff := s.members[i].set.Difference(s2.Children.members[j].set)
// We aren't permitted to add nodes with no elements.
if !diff.Empty() {
out.members = append(out.members, setNode{pathElement: s.members[i].pathElement, set: diff})
}
i++
}
j++
}
}
if i < len(s.members) {
out.members = append(out.members, s.members[i:]...)
}
return out
}
// Iterate calls f for each PathElement in the set.
func (s *SetNodeMap) Iterate(f func(PathElement)) {
for _, n := range s.members {
f(n.pathElement)
}
}
func (s *SetNodeMap) iteratePrefix(prefix Path, f func(Path)) {
for _, n := range s.members {
pe := n.pathElement
n.set.iteratePrefix(append(prefix, pe), f)
}
}

View File

@@ -0,0 +1,112 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package merge
import (
"fmt"
"sort"
"strings"
"sigs.k8s.io/structured-merge-diff/fieldpath"
)
// Conflict is a conflict on a specific field with the current manager of
// that field. It does implement the error interface so that it can be
// used as an error.
type Conflict struct {
Manager string
Path fieldpath.Path
}
// Conflict is an error.
var _ error = Conflict{}
// Error formats the conflict as an error.
func (c Conflict) Error() string {
return fmt.Sprintf("conflict with %q: %v", c.Manager, c.Path)
}
// Equals returns true if c == c2
func (c Conflict) Equals(c2 Conflict) bool {
if c.Manager != c2.Manager {
return false
}
return c.Path.Equals(c2.Path)
}
// Conflicts accumulates multiple conflicts and aggregates them by managers.
type Conflicts []Conflict
var _ error = Conflicts{}
// Error prints the list of conflicts, grouped by sorted managers.
func (conflicts Conflicts) Error() string {
if len(conflicts) == 1 {
return conflicts[0].Error()
}
m := map[string][]fieldpath.Path{}
for _, conflict := range conflicts {
m[conflict.Manager] = append(m[conflict.Manager], conflict.Path)
}
managers := []string{}
for manager := range m {
managers = append(managers, manager)
}
// Print conflicts by sorted managers.
sort.Strings(managers)
messages := []string{}
for _, manager := range managers {
messages = append(messages, fmt.Sprintf("conflicts with %q:", manager))
for _, path := range m[manager] {
messages = append(messages, fmt.Sprintf("- %v", path))
}
}
return strings.Join(messages, "\n")
}
// Equals returns true if the lists of conflicts are the same.
func (c Conflicts) Equals(c2 Conflicts) bool {
if len(c) != len(c2) {
return false
}
for i := range c {
if !c[i].Equals(c2[i]) {
return false
}
}
return true
}
// ConflictsFromManagers creates a list of conflicts given Managers sets.
func ConflictsFromManagers(sets fieldpath.ManagedFields) Conflicts {
conflicts := []Conflict{}
for manager, set := range sets {
set.Set().Iterate(func(p fieldpath.Path) {
conflicts = append(conflicts, Conflict{
Manager: manager,
Path: p,
})
})
}
return conflicts
}

View File

@@ -0,0 +1,288 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package merge
import (
"fmt"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/typed"
)
// Converter is an interface to the conversion logic. The converter
// needs to be able to convert objects from one version to another.
type Converter interface {
Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error)
IsMissingVersionError(error) bool
}
// Updater is the object used to compute updated FieldSets and also
// merge the object on Apply.
type Updater struct {
Converter Converter
enableUnions bool
}
// EnableUnionFeature turns on union handling. It is disabled by default until the
// feature is complete.
func (s *Updater) EnableUnionFeature() {
s.enableUnions = true
}
func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
conflicts := fieldpath.ManagedFields{}
removed := fieldpath.ManagedFields{}
compare, err := oldObject.Compare(newObject)
if err != nil {
return nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions := map[fieldpath.APIVersion]*typed.Comparison{
version: compare,
}
for manager, managerSet := range managers {
if manager == workflow {
continue
}
compare, ok := versions[managerSet.APIVersion()]
if !ok {
var err error
versionedOldObject, err := s.Converter.Convert(oldObject, managerSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
delete(managers, manager)
continue
}
return nil, fmt.Errorf("failed to convert old object: %v", err)
}
versionedNewObject, err := s.Converter.Convert(newObject, managerSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
delete(managers, manager)
continue
}
return nil, fmt.Errorf("failed to convert new object: %v", err)
}
compare, err = versionedOldObject.Compare(versionedNewObject)
if err != nil {
return nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions[managerSet.APIVersion()] = compare
}
conflictSet := managerSet.Set().Intersection(compare.Modified.Union(compare.Added))
if !conflictSet.Empty() {
conflicts[manager] = fieldpath.NewVersionedSet(conflictSet, managerSet.APIVersion(), false)
}
if !compare.Removed.Empty() {
removed[manager] = fieldpath.NewVersionedSet(compare.Removed, managerSet.APIVersion(), false)
}
}
if !force && len(conflicts) != 0 {
return nil, ConflictsFromManagers(conflicts)
}
for manager, conflictSet := range conflicts {
managers[manager] = fieldpath.NewVersionedSet(managers[manager].Set().Difference(conflictSet.Set()), managers[manager].APIVersion(), managers[manager].Applied())
}
for manager, removedSet := range removed {
managers[manager] = fieldpath.NewVersionedSet(managers[manager].Set().Difference(removedSet.Set()), managers[manager].APIVersion(), managers[manager].Applied())
}
for manager := range managers {
if managers[manager].Set().Empty() {
delete(managers, manager)
}
}
return managers, nil
}
// Update is the method you should call once you've merged your final
// object on CREATE/UPDATE/PATCH verbs. newObject must be the object
// that you intend to persist (after applying the patch if this is for a
// PATCH call), and liveObject must be the original object (empty if
// this is a CREATE call).
func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (*typed.TypedValue, fieldpath.ManagedFields, error) {
var err error
if s.enableUnions {
newObject, err = liveObject.NormalizeUnions(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
managers = shallowCopyManagers(managers)
managers, err = s.update(liveObject, newObject, version, managers, manager, true)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
compare, err := liveObject.Compare(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
}
if _, ok := managers[manager]; !ok {
managers[manager] = fieldpath.NewVersionedSet(fieldpath.NewSet(), version, false)
}
managers[manager] = fieldpath.NewVersionedSet(
managers[manager].Set().Union(compare.Modified).Union(compare.Added).Difference(compare.Removed),
version,
false,
)
if managers[manager].Set().Empty() {
delete(managers, manager)
}
return newObject, managers, nil
}
// Apply should be called when Apply is run, given the current object as
// well as the configuration that is applied. This will merge the object
// and return it.
func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (*typed.TypedValue, fieldpath.ManagedFields, error) {
managers = shallowCopyManagers(managers)
var err error
if s.enableUnions {
configObject, err = configObject.NormalizeUnionsApply(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
newObject, err := liveObject.Merge(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
}
if s.enableUnions {
newObject, err = configObject.NormalizeUnionsApply(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
lastSet := managers[manager]
set, err := configObject.ToFieldSet()
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
}
managers[manager] = fieldpath.NewVersionedSet(set, version, true)
newObject, err = s.prune(newObject, managers, manager, lastSet)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to prune fields: %v", err)
}
managers, err = s.update(liveObject, newObject, version, managers, manager, force)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
return newObject, managers, nil
}
func shallowCopyManagers(managers fieldpath.ManagedFields) fieldpath.ManagedFields {
newManagers := fieldpath.ManagedFields{}
for manager, set := range managers {
newManagers[manager] = set
}
return newManagers
}
// prune will remove a list or map item, iff:
// * applyingManager applied it last time
// * applyingManager didn't apply it this time
// * no other applier claims to manage it
func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet fieldpath.VersionedSet) (*typed.TypedValue, error) {
if lastSet == nil || lastSet.Set().Empty() {
return merged, nil
}
convertedMerged, err := s.Converter.Convert(merged, lastSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
return merged, nil
}
return nil, fmt.Errorf("failed to convert merged object to last applied version: %v", err)
}
pruned := convertedMerged.RemoveItems(lastSet.Set())
pruned, err = s.addBackOwnedItems(convertedMerged, pruned, managers, applyingManager)
if err != nil {
return nil, fmt.Errorf("failed add back owned items: %v", err)
}
pruned, err = s.addBackDanglingItems(convertedMerged, pruned, lastSet)
if err != nil {
return nil, fmt.Errorf("failed add back dangling items: %v", err)
}
return s.Converter.Convert(pruned, managers[applyingManager].APIVersion())
}
// addBackOwnedItems adds back any list and map items that were removed by prune,
// but other appliers (or the current applier's new config) claim to own.
func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (*typed.TypedValue, error) {
var err error
managedAtVersion := map[fieldpath.APIVersion]*fieldpath.Set{}
for _, managerSet := range managedFields {
if managerSet.Applied() {
if _, ok := managedAtVersion[managerSet.APIVersion()]; !ok {
managedAtVersion[managerSet.APIVersion()] = fieldpath.NewSet()
}
managedAtVersion[managerSet.APIVersion()] = managedAtVersion[managerSet.APIVersion()].Union(managerSet.Set())
}
}
for version, managed := range managedAtVersion {
merged, err = s.Converter.Convert(merged, version)
if err != nil {
if s.Converter.IsMissingVersionError(err) {
continue
}
return nil, fmt.Errorf("failed to convert merged object at version %v: %v", version, err)
}
pruned, err = s.Converter.Convert(pruned, version)
if err != nil {
if s.Converter.IsMissingVersionError(err) {
continue
}
return nil, fmt.Errorf("failed to convert pruned object at version %v: %v", version, err)
}
mergedSet, err := merged.ToFieldSet()
if err != nil {
return nil, fmt.Errorf("failed to create field set from merged object at version %v: %v", version, err)
}
prunedSet, err := pruned.ToFieldSet()
if err != nil {
return nil, fmt.Errorf("failed to create field set from pruned object at version %v: %v", version, err)
}
pruned = merged.RemoveItems(mergedSet.Difference(prunedSet.Union(managed)))
}
return pruned, nil
}
// addBackDanglingItems makes sure that the only items removed by prune are items that were
// previously owned by the currently applying manager. This will add back unowned items and items
// which are owned by Updaters that shouldn't be removed.
func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet fieldpath.VersionedSet) (*typed.TypedValue, error) {
convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
return merged, nil
}
return nil, fmt.Errorf("failed to convert pruned object to last applied version: %v", err)
}
prunedSet, err := convertedPruned.ToFieldSet()
if err != nil {
return nil, fmt.Errorf("failed to create field set from pruned object in last applied version: %v", err)
}
mergedSet, err := merged.ToFieldSet()
if err != nil {
return nil, fmt.Errorf("failed to create field set from merged object in last applied version: %v", err)
}
return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(lastSet.Set())), nil
}

28
vendor/sigs.k8s.io/structured-merge-diff/schema/doc.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
// Package schema defines a targeted schema language which allows one to
// represent all the schema information necessary to perform "structured"
// merges and diffs.
//
// Due to the targeted nature of the data model, the schema language can fit in
// just a few hundred lines of go code, making it much more understandable and
// concise than e.g. OpenAPI.
//
// This schema was derived by observing the API objects used by Kubernetes, and
// formalizing a model which allows certain operations ("apply") to be more
// well defined. It is currently missing one feature: one-of ("unions").
package schema

View File

@@ -0,0 +1,230 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package schema
// Schema is a list of named types.
type Schema struct {
Types []TypeDef `yaml:"types,omitempty"`
}
// A TypeSpecifier references a particular type in a schema.
type TypeSpecifier struct {
Type TypeRef `yaml:"type,omitempty"`
Schema Schema `yaml:"schema,omitempty"`
}
// TypeDef represents a named type in a schema.
type TypeDef struct {
// Top level types should be named. Every type must have a unique name.
Name string `yaml:"name,omitempty"`
Atom `yaml:"atom,omitempty,inline"`
}
// TypeRef either refers to a named type or declares an inlined type.
type TypeRef struct {
// Either the name or one member of Atom should be set.
NamedType *string `yaml:"namedType,omitempty"`
Inlined Atom `yaml:",inline,omitempty"`
}
// Atom represents the smallest possible pieces of the type system.
// Each set field in the Atom represents a possible type for the object.
// If none of the fields are set, any object will fail validation against the atom.
type Atom struct {
*Scalar `yaml:"scalar,omitempty"`
*List `yaml:"list,omitempty"`
*Map `yaml:"map,omitempty"`
}
// Scalar (AKA "primitive") represents a type which has a single value which is
// either numeric, string, or boolean.
//
// TODO: split numeric into float/int? Something even more fine-grained?
type Scalar string
const (
Numeric = Scalar("numeric")
String = Scalar("string")
Boolean = Scalar("boolean")
)
// ElementRelationship is an enum of the different possible relationships
// between the elements of container types (maps, lists).
type ElementRelationship string
const (
// Associative only applies to lists (see the documentation there).
Associative = ElementRelationship("associative")
// Atomic makes container types (lists, maps) behave
// as scalars / leaf fields
Atomic = ElementRelationship("atomic")
// Separable means the items of the container type have no particular
// relationship (default behavior for maps).
Separable = ElementRelationship("separable")
)
// Map is a key-value pair. Its default semantics are the same as an
// associative list, but:
// * It is serialized differently:
// map: {"k": {"value": "v"}}
// list: [{"key": "k", "value": "v"}]
// * Keys must be string typed.
// * Keys can't have multiple components.
//
// Optionally, maps may be atomic (for example, imagine representing an RGB
// color value--it doesn't make sense to have different actors own the R and G
// values).
//
// Maps may also represent a type which is composed of a number of different fields.
// Each field has a name and a type.
type Map struct {
// Each struct field appears exactly once in this list. The order in
// this list defines the canonical field ordering.
Fields []StructField `yaml:"fields,omitempty"`
// A Union is a grouping of fields with special rules. It may refer to
// one or more fields in the above list. A given field from the above
// list may be referenced in exactly 0 or 1 places in the below list.
// One can have multiple unions in the same struct, but the fields can't
// overlap between unions.
Unions []Union `yaml:"unions,omitempty"`
// ElementType is the type of the structs's unknown fields.
ElementType TypeRef `yaml:"elementType,omitempty"`
// ElementRelationship states the relationship between the map's items.
// * `separable` (or unset) implies that each element is 100% independent.
// * `atomic` implies that all elements depend on each other, and this
// is effectively a scalar / leaf field; it doesn't make sense for
// separate actors to set the elements. Example: an RGB color struct;
// it would never make sense to "own" only one component of the
// color.
// The default behavior for maps is `separable`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
}
// UnionFields are mapping between the fields that are part of the union and
// their discriminated value. The discriminated value has to be set, and
// should not conflict with other discriminated value in the list.
type UnionField struct {
// FieldName is the name of the field that is part of the union. This
// is the serialized form of the field.
FieldName string `yaml:"fieldName"`
// Discriminatorvalue is the value of the discriminator to
// select that field. If the union doesn't have a discriminator,
// this field is ignored.
DiscriminatorValue string `yaml:"discriminatorValue"`
}
// Union, or oneof, means that only one of multiple fields of a structure can be
// set at a time. Setting the discriminator helps clearing oher fields:
// - If discriminator changed to non-nil, and a new field has been added
// that doesn't match, an error is returned,
// - If discriminator hasn't changed and two fields or more are set, an
// error is returned,
// - If discriminator changed to non-nil, all other fields but the
// discriminated one will be cleared,
// - Otherwise, If only one field is left, update discriminator to that value.
type Union struct {
// Discriminator, if present, is the name of the field that
// discriminates fields in the union. The mapping between the value of
// the discriminator and the field is done by using the Fields list
// below.
Discriminator *string `yaml:"discriminator,omitempty"`
// DeduceInvalidDiscriminator indicates if the discriminator
// should be updated automatically based on the fields set. This
// typically defaults to false since we don't want to deduce by
// default (the behavior exists to maintain compatibility on
// existing types and shouldn't be used for new types).
DeduceInvalidDiscriminator bool `yaml:"deduceInvalidDiscriminator,omitempty"`
// This is the list of fields that belong to this union. All the
// fields present in here have to be part of the parent
// structure. Discriminator (if oneOf has one), is NOT included in
// this list. The value for field is how we map the name of the field
// to actual value for discriminator.
Fields []UnionField `yaml:"fields,omitempty"`
}
// StructField pairs a field name with a field type.
type StructField struct {
// Name is the field name.
Name string `yaml:"name,omitempty"`
// Type is the field type.
Type TypeRef `yaml:"type,omitempty"`
}
// List represents a type which contains a zero or more elements, all of the
// same subtype. Lists may be either associative: each element is more or less
// independent and could be managed by separate entities in the system; or
// atomic, where the elements are heavily dependent on each other: it is not
// sensible to change one element without considering the ramifications on all
// the other elements.
type List struct {
// ElementType is the type of the list's elements.
ElementType TypeRef `yaml:"elementType,omitempty"`
// ElementRelationship states the relationship between the list's elements
// and must have one of these values:
// * `atomic`: the list is treated as a single entity, like a scalar.
// * `associative`:
// - If the list element is a scalar, the list is treated as a set.
// - If the list element is a map, the list is treated as a map.
// There is no default for this value for lists; all schemas must
// explicitly state the element relationship for all lists.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
// Iff ElementRelationship is `associative`, and the element type is
// map, then Keys must have non-zero length, and it lists the fields
// of the element's map type which are to be used as the keys of the
// list.
//
// TODO: change this to "non-atomic struct" above and make the code reflect this.
//
// Each key must refer to a single field name (no nesting, not JSONPath).
Keys []string `yaml:"keys,omitempty"`
}
// FindNamedType is a convenience function that returns the referenced TypeDef,
// if it exists, or (nil, false) if it doesn't.
func (s Schema) FindNamedType(name string) (TypeDef, bool) {
for _, t := range s.Types {
if t.Name == name {
return t, true
}
}
return TypeDef{}, false
}
// Resolve is a convenience function which returns the atom referenced, whether
// it is inline or named. Returns (Atom{}, false) if the type can't be resolved.
//
// This allows callers to not care about the difference between a (possibly
// inlined) reference and a definition.
func (s *Schema) Resolve(tr TypeRef) (Atom, bool) {
if tr.NamedType != nil {
t, ok := s.FindNamedType(*tr.NamedType)
if !ok {
return Atom{}, false
}
return t.Atom, true
}
return tr.Inlined, true
}

View File

@@ -0,0 +1,166 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package schema
// Equals returns true iff the two Schemas are equal.
func (a Schema) Equals(b Schema) bool {
if len(a.Types) != len(b.Types) {
return false
}
for i := range a.Types {
if !a.Types[i].Equals(b.Types[i]) {
return false
}
}
return true
}
// Equals returns true iff the two TypeRefs are equal.
//
// Note that two typerefs that have an equivalent type but where one is
// inlined and the other is named, are not considered equal.
func (a TypeRef) Equals(b TypeRef) bool {
if (a.NamedType == nil) != (b.NamedType == nil) {
return false
}
if a.NamedType != nil {
if *a.NamedType != *b.NamedType {
return false
}
//return true
}
return a.Inlined.Equals(b.Inlined)
}
// Equals returns true iff the two TypeDefs are equal.
func (a TypeDef) Equals(b TypeDef) bool {
if a.Name != b.Name {
return false
}
return a.Atom.Equals(b.Atom)
}
// Equals returns true iff the two Atoms are equal.
func (a Atom) Equals(b Atom) bool {
if (a.Scalar == nil) != (b.Scalar == nil) {
return false
}
if (a.List == nil) != (b.List == nil) {
return false
}
if (a.Map == nil) != (b.Map == nil) {
return false
}
switch {
case a.Scalar != nil:
return *a.Scalar == *b.Scalar
case a.List != nil:
return a.List.Equals(*b.List)
case a.Map != nil:
return a.Map.Equals(*b.Map)
}
return true
}
// Equals returns true iff the two Maps are equal.
func (a Map) Equals(b Map) bool {
if !a.ElementType.Equals(b.ElementType) {
return false
}
if a.ElementRelationship != b.ElementRelationship {
return false
}
if len(a.Fields) != len(b.Fields) {
return false
}
for i := range a.Fields {
if !a.Fields[i].Equals(b.Fields[i]) {
return false
}
}
if len(a.Unions) != len(b.Unions) {
return false
}
for i := range a.Unions {
if !a.Unions[i].Equals(b.Unions[i]) {
return false
}
}
return true
}
// Equals returns true iff the two Unions are equal.
func (a Union) Equals(b Union) bool {
if (a.Discriminator == nil) != (b.Discriminator == nil) {
return false
}
if a.Discriminator != nil {
if *a.Discriminator != *b.Discriminator {
return false
}
}
if a.DeduceInvalidDiscriminator != b.DeduceInvalidDiscriminator {
return false
}
if len(a.Fields) != len(b.Fields) {
return false
}
for i := range a.Fields {
if !a.Fields[i].Equals(b.Fields[i]) {
return false
}
}
return true
}
// Equals returns true iff the two UnionFields are equal.
func (a UnionField) Equals(b UnionField) bool {
if a.FieldName != b.FieldName {
return false
}
if a.DiscriminatorValue != b.DiscriminatorValue {
return false
}
return true
}
// Equals returns true iff the two StructFields are equal.
func (a StructField) Equals(b StructField) bool {
if a.Name != b.Name {
return false
}
return a.Type.Equals(b.Type)
}
// Equals returns true iff the two Lists are equal.
func (a List) Equals(b List) bool {
if !a.ElementType.Equals(b.ElementType) {
return false
}
if a.ElementRelationship != b.ElementRelationship {
return false
}
if len(a.Keys) != len(b.Keys) {
return false
}
for i := range a.Keys {
if a.Keys[i] != b.Keys[i] {
return false
}
}
return true
}

View File

@@ -0,0 +1,148 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package schema
// SchemaSchemaYAML is a schema against which you can validate other schemas.
// It will validate itself. It can be unmarshalled into a Schema type.
var SchemaSchemaYAML = `types:
- name: schema
map:
fields:
- name: types
type:
list:
elementRelationship: associative
elementType:
namedType: typeDef
keys:
- name
- name: typeDef
map:
fields:
- name: name
type:
scalar: string
- name: scalar
type:
scalar: string
- name: map
type:
namedType: map
- name: list
type:
namedType: list
- name: untyped
type:
namedType: untyped
- name: typeRef
map:
fields:
- name: namedType
type:
scalar: string
- name: scalar
type:
scalar: string
- name: map
type:
namedType: map
- name: list
type:
namedType: list
- name: untyped
type:
namedType: untyped
- name: scalar
scalar: string
- name: map
map:
fields:
- name: fields
type:
list:
elementType:
namedType: structField
elementRelationship: associative
keys: [ "name" ]
- name: unions
type:
list:
elementType:
namedType: union
elementRelationship: atomic
- name: elementType
type:
namedType: typeRef
- name: elementRelationship
type:
scalar: string
- name: unionField
map:
fields:
- name: fieldName
type:
scalar: string
- name: discriminatorValue
type:
scalar: string
- name: union
map:
fields:
- name: discriminator
type:
scalar: string
- name: deduceInvalidDiscriminator
type:
scalar: bool
- name: fields
type:
list:
elementRelationship: associative
elementType:
namedType: unionField
keys:
- fieldName
- name: structField
map:
fields:
- name: name
type:
scalar: string
- name: type
type:
namedType: typeRef
- name: list
map:
fields:
- name: elementType
type:
namedType: typeRef
- name: elementRelationship
type:
scalar: string
- name: keys
type:
list:
elementType:
scalar: string
- name: untyped
map:
fields:
- name: elementRelationship
type:
scalar: string
`

18
vendor/sigs.k8s.io/structured-merge-diff/typed/doc.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
// Package typed contains logic for operating on values with given schemas.
package typed

View File

@@ -0,0 +1,256 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package typed
import (
"errors"
"fmt"
"strings"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
// ValidationError reports an error about a particular field
type ValidationError struct {
Path fieldpath.Path
ErrorMessage string
}
// Error returns a human readable error message.
func (ve ValidationError) Error() string {
if len(ve.Path) == 0 {
return ve.ErrorMessage
}
return fmt.Sprintf("%s: %v", ve.Path, ve.ErrorMessage)
}
// ValidationErrors accumulates multiple validation error messages.
type ValidationErrors []ValidationError
// Error returns a human readable error message reporting each error in the
// list.
func (errs ValidationErrors) Error() string {
if len(errs) == 1 {
return errs[0].Error()
}
messages := []string{"errors:"}
for _, e := range errs {
messages = append(messages, " "+e.Error())
}
return strings.Join(messages, "\n")
}
// errorFormatter makes it easy to keep a list of validation errors. They
// should all be packed into a single error object before leaving the package
// boundary, since it's weird to have functions not return a plain error type.
type errorFormatter struct {
path fieldpath.Path
}
func (ef *errorFormatter) descend(pe fieldpath.PathElement) {
ef.path = append(ef.path, pe)
}
// parent returns the parent, for the purpose of buffer reuse. It's an error to
// call this if there is no parent.
func (ef *errorFormatter) parent() errorFormatter {
return errorFormatter{
path: ef.path[:len(ef.path)-1],
}
}
func (ef errorFormatter) errorf(format string, args ...interface{}) ValidationErrors {
return ValidationErrors{{
Path: append(fieldpath.Path{}, ef.path...),
ErrorMessage: fmt.Sprintf(format, args...),
}}
}
func (ef errorFormatter) error(err error) ValidationErrors {
return ValidationErrors{{
Path: append(fieldpath.Path{}, ef.path...),
ErrorMessage: err.Error(),
}}
}
func (ef errorFormatter) prefixError(prefix string, err error) ValidationErrors {
return ValidationErrors{{
Path: append(fieldpath.Path{}, ef.path...),
ErrorMessage: prefix + err.Error(),
}}
}
type atomHandler interface {
doScalar(schema.Scalar) ValidationErrors
doList(schema.List) ValidationErrors
doMap(schema.Map) ValidationErrors
errorf(msg string, args ...interface{}) ValidationErrors
}
func resolveSchema(s *schema.Schema, tr schema.TypeRef, v *value.Value, ah atomHandler) ValidationErrors {
a, ok := s.Resolve(tr)
if !ok {
return ah.errorf("schema error: no type found matching: %v", *tr.NamedType)
}
a = deduceAtom(a, v)
return handleAtom(a, tr, ah)
}
func deduceAtom(a schema.Atom, v *value.Value) schema.Atom {
switch {
case v == nil:
case v.FloatValue != nil, v.IntValue != nil, v.StringValue != nil, v.BooleanValue != nil:
return schema.Atom{Scalar: a.Scalar}
case v.ListValue != nil:
return schema.Atom{List: a.List}
case v.MapValue != nil:
return schema.Atom{Map: a.Map}
}
return a
}
func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErrors {
switch {
case a.Map != nil:
return ah.doMap(*a.Map)
case a.Scalar != nil:
return ah.doScalar(*a.Scalar)
case a.List != nil:
return ah.doList(*a.List)
}
name := "inlined"
if tr.NamedType != nil {
name = "named type: " + *tr.NamedType
}
return ah.errorf("schema error: invalid atom: %v", name)
}
func (ef errorFormatter) validateScalar(t schema.Scalar, v *value.Value, prefix string) (errs ValidationErrors) {
if v == nil {
return nil
}
if v.Null {
return nil
}
switch t {
case schema.Numeric:
if v.FloatValue == nil && v.IntValue == nil {
// TODO: should the schema separate int and float?
return ef.errorf("%vexpected numeric (int or float), got %v", prefix, v)
}
case schema.String:
if v.StringValue == nil {
return ef.errorf("%vexpected string, got %v", prefix, v)
}
case schema.Boolean:
if v.BooleanValue == nil {
return ef.errorf("%vexpected boolean, got %v", prefix, v)
}
}
return nil
}
// Returns the list, or an error. Reminder: nil is a valid list and might be returned.
func listValue(val value.Value) (*value.List, error) {
switch {
case val.Null:
// Null is a valid list.
return nil, nil
case val.ListValue != nil:
return val.ListValue, nil
default:
return nil, fmt.Errorf("expected list, got %v", val)
}
}
// Returns the map, or an error. Reminder: nil is a valid map and might be returned.
func mapValue(val value.Value) (*value.Map, error) {
switch {
case val.Null:
return nil, nil
case val.MapValue != nil:
return val.MapValue, nil
default:
return nil, fmt.Errorf("expected map, got %v", val)
}
}
func keyedAssociativeListItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
pe := fieldpath.PathElement{}
if child.Null {
// For now, the keys are required which means that null entries
// are illegal.
return pe, errors.New("associative list with keys may not have a null element")
}
if child.MapValue == nil {
return pe, errors.New("associative list with keys may not have non-map elements")
}
keyMap := &value.Map{}
for _, fieldName := range list.Keys {
var fieldValue value.Value
field, ok := child.MapValue.Get(fieldName)
if ok {
fieldValue = field.Value
} else {
// Treat keys as required.
return pe, fmt.Errorf("associative list with keys has an element that omits key field %q", fieldName)
}
keyMap.Set(fieldName, fieldValue)
}
pe.Key = keyMap
return pe, nil
}
func setItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
pe := fieldpath.PathElement{}
switch {
case child.MapValue != nil:
// TODO: atomic maps should be acceptable.
return pe, errors.New("associative list without keys has an element that's a map type")
case child.ListValue != nil:
// Should we support a set of lists? For the moment
// let's say we don't.
// TODO: atomic lists should be acceptable.
return pe, errors.New("not supported: associative list with lists as elements")
case child.Null:
return pe, errors.New("associative list without keys has an element that's an explicit null")
default:
// We are a set type.
pe.Value = &child
return pe, nil
}
}
func listItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
if list.ElementRelationship == schema.Associative {
if len(list.Keys) > 0 {
return keyedAssociativeListItemToPathElement(list, index, child)
}
// If there's no keys, then we must be a set of primitives.
return setItemToPathElement(list, index, child)
}
// Use the index as a key for atomic lists.
return fieldpath.PathElement{Index: &index}, nil
}

370
vendor/sigs.k8s.io/structured-merge-diff/typed/merge.go generated vendored Normal file
View File

@@ -0,0 +1,370 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package typed
import (
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
type mergingWalker struct {
errorFormatter
lhs *value.Value
rhs *value.Value
schema *schema.Schema
typeRef schema.TypeRef
// How to merge. Called after schema validation for all leaf fields.
rule mergeRule
// If set, called after non-leaf items have been merged. (`out` is
// probably already set.)
postItemHook mergeRule
// output of the merge operation (nil if none)
out *value.Value
// internal housekeeping--don't set when constructing.
inLeaf bool // Set to true if we're in a "big leaf"--atomic map/list
// Allocate only as many walkers as needed for the depth by storing them here.
spareWalkers *[]*mergingWalker
}
// merge rules examine w.lhs and w.rhs (up to one of which may be nil) and
// optionally set w.out. If lhs and rhs are both set, they will be of
// comparable type.
type mergeRule func(w *mergingWalker)
var (
ruleKeepRHS = mergeRule(func(w *mergingWalker) {
if w.rhs != nil {
v := *w.rhs
w.out = &v
} else if w.lhs != nil {
v := *w.lhs
w.out = &v
}
})
)
// merge sets w.out.
func (w *mergingWalker) merge() (errs ValidationErrors) {
if w.lhs == nil && w.rhs == nil {
// check this condidition here instead of everywhere below.
return w.errorf("at least one of lhs and rhs must be provided")
}
a, ok := w.schema.Resolve(w.typeRef)
if !ok {
return w.errorf("schema error: no type found matching: %v", *w.typeRef.NamedType)
}
alhs := deduceAtom(a, w.lhs)
arhs := deduceAtom(a, w.rhs)
if alhs.Equals(arhs) {
errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
} else {
w2 := *w
errs = append(errs, handleAtom(alhs, w.typeRef, &w2)...)
errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
}
if !w.inLeaf && w.postItemHook != nil {
w.postItemHook(w)
}
return errs
}
// doLeaf should be called on leaves before descending into children, if there
// will be a descent. It modifies w.inLeaf.
func (w *mergingWalker) doLeaf() {
if w.inLeaf {
// We're in a "big leaf", an atomic map or list. Ignore
// subsequent leaves.
return
}
w.inLeaf = true
// We don't recurse into leaf fields for merging.
w.rule(w)
}
func (w *mergingWalker) doScalar(t schema.Scalar) (errs ValidationErrors) {
errs = append(errs, w.validateScalar(t, w.lhs, "lhs: ")...)
errs = append(errs, w.validateScalar(t, w.rhs, "rhs: ")...)
if len(errs) > 0 {
return errs
}
// All scalars are leaf fields.
w.doLeaf()
return nil
}
func (w *mergingWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef) *mergingWalker {
if w.spareWalkers == nil {
// first descent.
w.spareWalkers = &[]*mergingWalker{}
}
var w2 *mergingWalker
if n := len(*w.spareWalkers); n > 0 {
w2, *w.spareWalkers = (*w.spareWalkers)[n-1], (*w.spareWalkers)[:n-1]
} else {
w2 = &mergingWalker{}
}
*w2 = *w
w2.typeRef = tr
w2.errorFormatter.descend(pe)
w2.lhs = nil
w2.rhs = nil
w2.out = nil
return w2
}
func (w *mergingWalker) finishDescent(w2 *mergingWalker) {
// if the descent caused a realloc, ensure that we reuse the buffer
// for the next sibling.
w.errorFormatter = w2.errorFormatter.parent()
*w.spareWalkers = append(*w.spareWalkers, w2)
}
func (w *mergingWalker) derefMap(prefix string, v *value.Value, dest **value.Map) (errs ValidationErrors) {
// taking dest as input so that it can be called as a one-liner with
// append.
if v == nil {
return nil
}
m, err := mapValue(*v)
if err != nil {
return w.prefixError(prefix, err)
}
*dest = m
return nil
}
func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (errs ValidationErrors) {
out := &value.List{}
// TODO: ordering is totally wrong.
// TODO: might as well make the map order work the same way.
// This is a cheap hack to at least make the output order stable.
rhsOrder := []fieldpath.PathElement{}
// First, collect all RHS children.
observedRHS := map[string]value.Value{}
if rhs != nil {
for i, child := range rhs.Items {
pe, err := listItemToPathElement(t, i, child)
if err != nil {
errs = append(errs, w.errorf("rhs: element %v: %v", i, err.Error())...)
// If we can't construct the path element, we can't
// even report errors deeper in the schema, so bail on
// this element.
continue
}
keyStr := pe.String()
if _, found := observedRHS[keyStr]; found {
errs = append(errs, w.errorf("rhs: duplicate entries for key %v", keyStr)...)
}
observedRHS[keyStr] = child
rhsOrder = append(rhsOrder, pe)
}
}
// Then merge with LHS children.
observedLHS := map[string]struct{}{}
if lhs != nil {
for i, child := range lhs.Items {
pe, err := listItemToPathElement(t, i, child)
if err != nil {
errs = append(errs, w.errorf("lhs: element %v: %v", i, err.Error())...)
// If we can't construct the path element, we can't
// even report errors deeper in the schema, so bail on
// this element.
continue
}
keyStr := pe.String()
if _, found := observedLHS[keyStr]; found {
errs = append(errs, w.errorf("lhs: duplicate entries for key %v", keyStr)...)
continue
}
observedLHS[keyStr] = struct{}{}
w2 := w.prepareDescent(pe, t.ElementType)
w2.lhs = &child
if rchild, ok := observedRHS[keyStr]; ok {
w2.rhs = &rchild
}
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Items = append(out.Items, *w2.out)
}
w.finishDescent(w2)
// Keep track of children that have been handled
delete(observedRHS, keyStr)
}
}
for _, rhsToCheck := range rhsOrder {
if unmergedChild, ok := observedRHS[rhsToCheck.String()]; ok {
w2 := w.prepareDescent(rhsToCheck, t.ElementType)
w2.rhs = &unmergedChild
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Items = append(out.Items, *w2.out)
}
w.finishDescent(w2)
}
}
if len(out.Items) > 0 {
w.out = &value.Value{ListValue: out}
}
return errs
}
func (w *mergingWalker) derefList(prefix string, v *value.Value, dest **value.List) (errs ValidationErrors) {
// taking dest as input so that it can be called as a one-liner with
// append.
if v == nil {
return nil
}
l, err := listValue(*v)
if err != nil {
return w.prefixError(prefix, err)
}
*dest = l
return nil
}
func (w *mergingWalker) doList(t schema.List) (errs ValidationErrors) {
var lhs, rhs *value.List
w.derefList("lhs: ", w.lhs, &lhs)
w.derefList("rhs: ", w.rhs, &rhs)
// If both lhs and rhs are empty/null, treat it as a
// leaf: this helps preserve the empty/null
// distinction.
emptyPromoteToLeaf := (lhs == nil || len(lhs.Items) == 0) &&
(rhs == nil || len(rhs.Items) == 0)
if t.ElementRelationship == schema.Atomic || emptyPromoteToLeaf {
w.doLeaf()
return nil
}
if lhs == nil && rhs == nil {
return nil
}
errs = w.visitListItems(t, lhs, rhs)
return errs
}
func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs ValidationErrors) {
out := &value.Map{}
fieldTypes := map[string]schema.TypeRef{}
for i := range t.Fields {
// I don't want to use the loop variable since a reference
// might outlive the loop iteration (in an error message).
f := t.Fields[i]
fieldTypes[f.Name] = f.Type
}
if lhs != nil {
for i := range lhs.Items {
litem := &lhs.Items[i]
fieldType := t.ElementType
if ft, ok := fieldTypes[litem.Name]; ok {
fieldType = ft
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &litem.Name}, fieldType)
w2.lhs = &litem.Value
if rhs != nil {
if ritem, ok := rhs.Get(litem.Name); ok {
w2.rhs = &ritem.Value
}
}
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Items = append(out.Items, value.Field{litem.Name, *w2.out})
}
w.finishDescent(w2)
}
}
if rhs != nil {
for j := range rhs.Items {
ritem := &rhs.Items[j]
if lhs != nil {
if _, ok := lhs.Get(ritem.Name); ok {
continue
}
}
fieldType := t.ElementType
if ft, ok := fieldTypes[ritem.Name]; ok {
fieldType = ft
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &ritem.Name}, fieldType)
w2.rhs = &ritem.Value
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Items = append(out.Items, value.Field{ritem.Name, *w2.out})
}
w.finishDescent(w2)
}
}
if len(out.Items) > 0 {
w.out = &value.Value{MapValue: out}
}
return errs
}
func (w *mergingWalker) doMap(t schema.Map) (errs ValidationErrors) {
var lhs, rhs *value.Map
w.derefMap("lhs: ", w.lhs, &lhs)
w.derefMap("rhs: ", w.rhs, &rhs)
// If both lhs and rhs are empty/null, treat it as a
// leaf: this helps preserve the empty/null
// distinction.
emptyPromoteToLeaf := (lhs == nil || len(lhs.Items) == 0) &&
(rhs == nil || len(rhs.Items) == 0)
if t.ElementRelationship == schema.Atomic || emptyPromoteToLeaf {
w.doLeaf()
return nil
}
if lhs == nil && rhs == nil {
return nil
}
errs = append(errs, w.visitMapItems(t, lhs, rhs)...)
return errs
}

View File

@@ -0,0 +1,137 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package typed
import (
"fmt"
yaml "gopkg.in/yaml.v2"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
// YAMLObject is an object encoded in YAML.
type YAMLObject string
// Parser implements YAMLParser and allows introspecting the schema.
type Parser struct {
Schema schema.Schema
}
// create builds an unvalidated parser.
func create(s YAMLObject) (*Parser, error) {
p := Parser{}
err := yaml.Unmarshal([]byte(s), &p.Schema)
return &p, err
}
func createOrDie(schema YAMLObject) *Parser {
p, err := create(schema)
if err != nil {
panic(fmt.Errorf("failed to create parser: %v", err))
}
return p
}
var ssParser = createOrDie(YAMLObject(schema.SchemaSchemaYAML))
// NewParser will build a YAMLParser from a schema. The schema is validated.
func NewParser(schema YAMLObject) (*Parser, error) {
_, err := ssParser.Type("schema").FromYAML(schema)
if err != nil {
return nil, fmt.Errorf("unable to validate schema: %v", err)
}
p, err := create(schema)
if err != nil {
return nil, err
}
return p, nil
}
// TypeNames returns a list of types this parser understands.
func (p *Parser) TypeNames() (names []string) {
for _, td := range p.Schema.Types {
names = append(names, td.Name)
}
return names
}
// Type returns a helper which can produce objects of the given type. Any
// errors are deferred until a further function is called.
func (p *Parser) Type(name string) ParseableType {
return ParseableType{
Schema: &p.Schema,
TypeRef: schema.TypeRef{NamedType: &name},
}
}
// ParseableType allows for easy production of typed objects.
type ParseableType struct {
TypeRef schema.TypeRef
Schema *schema.Schema
}
// IsValid return true if p's schema and typename are valid.
func (p ParseableType) IsValid() bool {
_, ok := p.Schema.Resolve(p.TypeRef)
return ok
}
// FromYAML parses a yaml string into an object with the current schema
// and the type "typename" or an error if validation fails.
func (p ParseableType) FromYAML(object YAMLObject) (*TypedValue, error) {
v, err := value.FromYAML([]byte(object))
if err != nil {
return nil, err
}
return AsTyped(v, p.Schema, p.TypeRef)
}
// FromUnstructured converts a go interface to a TypedValue. It will return an
// error if the resulting object fails schema validation.
func (p ParseableType) FromUnstructured(in interface{}) (*TypedValue, error) {
v, err := value.FromUnstructured(in)
if err != nil {
return nil, err
}
return AsTyped(v, p.Schema, p.TypeRef)
}
// DeducedParseableType is a ParseableType that deduces the type from
// the content of the object.
var DeducedParseableType ParseableType = createOrDie(YAMLObject(`types:
- name: __untyped_atomic_
scalar: untyped
list:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
map:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
- name: __untyped_deduced_
scalar: untyped
list:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
map:
elementType:
namedType: __untyped_deduced_
elementRelationship: separable
`)).Type("__untyped_deduced_")

View File

@@ -0,0 +1,112 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package typed
import (
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
type removingWalker struct {
value *value.Value
schema *schema.Schema
toRemove *fieldpath.Set
}
func removeItemsWithSchema(value *value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef) {
w := &removingWalker{
value: value,
schema: schema,
toRemove: toRemove,
}
resolveSchema(schema, typeRef, value, w)
}
// doLeaf should be called on leaves before descending into children, if there
// will be a descent. It modifies w.inLeaf.
func (w *removingWalker) doLeaf() ValidationErrors { return nil }
func (w *removingWalker) doScalar(t schema.Scalar) ValidationErrors { return nil }
func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) {
l := w.value.ListValue
// If list is null, empty, or atomic just return
if l == nil || len(l.Items) == 0 || t.ElementRelationship == schema.Atomic {
return nil
}
newItems := []value.Value{}
for i := range l.Items {
item := l.Items[i]
// Ignore error because we have already validated this list
pe, _ := listItemToPathElement(t, i, item)
path, _ := fieldpath.MakePath(pe)
if w.toRemove.Has(path) {
continue
}
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
removeItemsWithSchema(&l.Items[i], subset, w.schema, t.ElementType)
}
newItems = append(newItems, l.Items[i])
}
l.Items = newItems
if len(l.Items) == 0 {
w.value.ListValue = nil
w.value.Null = true
}
return nil
}
func (w *removingWalker) doMap(t schema.Map) ValidationErrors {
m := w.value.MapValue
// If map is null, empty, or atomic just return
if m == nil || len(m.Items) == 0 || t.ElementRelationship == schema.Atomic {
return nil
}
fieldTypes := map[string]schema.TypeRef{}
for _, structField := range t.Fields {
fieldTypes[structField.Name] = structField.Type
}
newMap := &value.Map{}
for i := range m.Items {
item := m.Items[i]
pe := fieldpath.PathElement{FieldName: &item.Name}
path, _ := fieldpath.MakePath(pe)
fieldType := t.ElementType
if ft, ok := fieldTypes[item.Name]; ok {
fieldType = ft
} else {
if w.toRemove.Has(path) {
continue
}
}
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
removeItemsWithSchema(&m.Items[i].Value, subset, w.schema, fieldType)
}
newMap.Set(item.Name, m.Items[i].Value)
}
w.value.MapValue = newMap
if len(w.value.MapValue.Items) == 0 {
w.value.MapValue = nil
w.value.Null = true
}
return nil
}
func (*removingWalker) errorf(_ string, _ ...interface{}) ValidationErrors { return nil }

303
vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go generated vendored Normal file
View File

@@ -0,0 +1,303 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package typed
import (
"fmt"
"sync"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
// AsTyped accepts a value and a type and returns a TypedValue. 'v' must have
// type 'typeName' in the schema. An error is returned if the v doesn't conform
// to the schema.
func AsTyped(v value.Value, s *schema.Schema, typeRef schema.TypeRef) (*TypedValue, error) {
tv := &TypedValue{
value: v,
typeRef: typeRef,
schema: s,
}
if err := tv.Validate(); err != nil {
return nil, err
}
return tv, nil
}
// AsTypeUnvalidated is just like AsTyped, but doesn't validate that the type
// conforms to the schema, for cases where that has already been checked or
// where you're going to call a method that validates as a side-effect (like
// ToFieldSet).
func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeRef schema.TypeRef) *TypedValue {
tv := &TypedValue{
value: v,
typeRef: typeRef,
schema: s,
}
return tv
}
// TypedValue is a value of some specific type.
type TypedValue struct {
value value.Value
typeRef schema.TypeRef
schema *schema.Schema
}
// AsValue removes the type from the TypedValue and only keeps the value.
func (tv TypedValue) AsValue() *value.Value {
return &tv.value
}
// Validate returns an error with a list of every spec violation.
func (tv TypedValue) Validate() error {
w := tv.walker()
defer w.finished()
if errs := w.validate(); len(errs) != 0 {
return errs
}
return nil
}
// ToFieldSet creates a set containing every leaf field and item mentioned, or
// validation errors, if any were encountered.
func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) {
s := fieldpath.NewSet()
w := tv.walker()
defer w.finished()
w.leafFieldCallback = func(p fieldpath.Path) { s.Insert(p) }
w.nodeFieldCallback = func(p fieldpath.Path) { s.Insert(p) }
if errs := w.validate(); len(errs) != 0 {
return nil, errs
}
return s, nil
}
// Merge returns the result of merging tv and pso ("partially specified
// object") together. Of note:
// * No fields can be removed by this operation.
// * If both tv and pso specify a given leaf field, the result will keep pso's
// value.
// * Container typed elements will have their items ordered:
// * like tv, if pso doesn't change anything in the container
// * like pso, if pso does change something in the container.
// tv and pso must both be of the same type (their Schema and TypeRef must
// match), or an error will be returned. Validation errors will be returned if
// the objects don't conform to the schema.
func (tv TypedValue) Merge(pso *TypedValue) (*TypedValue, error) {
return merge(&tv, pso, ruleKeepRHS, nil)
}
// Compare compares the two objects. See the comments on the `Comparison`
// struct for details on the return value.
//
// tv and rhs must both be of the same type (their Schema and TypeRef must
// match), or an error will be returned. Validation errors will be returned if
// the objects don't conform to the schema.
func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
c = &Comparison{
Removed: fieldpath.NewSet(),
Modified: fieldpath.NewSet(),
Added: fieldpath.NewSet(),
}
c.Merged, err = merge(&tv, rhs, func(w *mergingWalker) {
if w.lhs == nil {
c.Added.Insert(w.path)
} else if w.rhs == nil {
c.Removed.Insert(w.path)
} else if !w.rhs.Equals(*w.lhs) {
// TODO: Equality is not sufficient for this.
// Need to implement equality check on the value type.
c.Modified.Insert(w.path)
}
ruleKeepRHS(w)
}, func(w *mergingWalker) {
if w.lhs == nil {
c.Added.Insert(w.path)
} else if w.rhs == nil {
c.Removed.Insert(w.path)
}
})
if err != nil {
return nil, err
}
return c, nil
}
// RemoveItems removes each provided list or map item from the value.
func (tv TypedValue) RemoveItems(items *fieldpath.Set) *TypedValue {
tv.value, _ = value.FromUnstructured(tv.value.ToUnstructured(true))
removeItemsWithSchema(&tv.value, items, tv.schema, tv.typeRef)
return &tv
}
// NormalizeUnions takes the new object and normalizes the union:
// - If discriminator changed to non-nil, and a new field has been added
// that doesn't match, an error is returned,
// - If discriminator hasn't changed and two fields or more are set, an
// error is returned,
// - If discriminator changed to non-nil, all other fields but the
// discriminated one will be cleared,
// - Otherwise, If only one field is left, update discriminator to that value.
//
// Please note: union behavior isn't finalized yet and this is still experimental.
func (tv TypedValue) NormalizeUnions(new *TypedValue) (*TypedValue, error) {
var errs ValidationErrors
var normalizeFn = func(w *mergingWalker) {
if w.rhs != nil {
v := *w.rhs
w.out = &v
}
if err := normalizeUnions(w); err != nil {
errs = append(errs, w.error(err)...)
}
}
out, mergeErrs := merge(&tv, new, func(w *mergingWalker) {}, normalizeFn)
if mergeErrs != nil {
errs = append(errs, mergeErrs.(ValidationErrors)...)
}
if len(errs) > 0 {
return nil, errs
}
return out, nil
}
// NormalizeUnionsApply specifically normalize unions on apply. It
// validates that the applied union is correct (there should be no
// ambiguity there), and clear the fields according to the sent intent.
//
// Please note: union behavior isn't finalized yet and this is still experimental.
func (tv TypedValue) NormalizeUnionsApply(new *TypedValue) (*TypedValue, error) {
var errs ValidationErrors
var normalizeFn = func(w *mergingWalker) {
if w.rhs != nil {
v := *w.rhs
w.out = &v
}
if err := normalizeUnionsApply(w); err != nil {
errs = append(errs, w.error(err)...)
}
}
out, mergeErrs := merge(&tv, new, func(w *mergingWalker) {}, normalizeFn)
if mergeErrs != nil {
errs = append(errs, mergeErrs.(ValidationErrors)...)
}
if len(errs) > 0 {
return nil, errs
}
return out, nil
}
func (tv TypedValue) Empty() *TypedValue {
tv.value = value.Value{Null: true}
return &tv
}
var mwPool = sync.Pool{
New: func() interface{} { return &mergingWalker{} },
}
func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error) {
if lhs.schema != rhs.schema {
return nil, errorFormatter{}.
errorf("expected objects with types from the same schema")
}
if !lhs.typeRef.Equals(rhs.typeRef) {
return nil, errorFormatter{}.
errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
}
mw := mwPool.Get().(*mergingWalker)
defer func() {
mw.lhs = nil
mw.rhs = nil
mw.schema = nil
mw.typeRef = schema.TypeRef{}
mw.rule = nil
mw.postItemHook = nil
mw.out = nil
mw.inLeaf = false
mwPool.Put(mw)
}()
mw.lhs = &lhs.value
mw.rhs = &rhs.value
mw.schema = lhs.schema
mw.typeRef = lhs.typeRef
mw.rule = rule
mw.postItemHook = postRule
errs := mw.merge()
if len(errs) > 0 {
return nil, errs
}
out := &TypedValue{
schema: lhs.schema,
typeRef: lhs.typeRef,
}
if mw.out == nil {
out.value = value.Value{Null: true}
} else {
out.value = *mw.out
}
return out, nil
}
// Comparison is the return value of a TypedValue.Compare() operation.
//
// No field will appear in more than one of the three fieldsets. If all of the
// fieldsets are empty, then the objects must have been equal.
type Comparison struct {
// Merged is the result of merging the two objects, as explained in the
// comments on TypedValue.Merge().
Merged *TypedValue
// Removed contains any fields removed by rhs (the right-hand-side
// object in the comparison).
Removed *fieldpath.Set
// Modified contains fields present in both objects but different.
Modified *fieldpath.Set
// Added contains any fields added by rhs.
Added *fieldpath.Set
}
// IsSame returns true if the comparison returned no changes (the two
// compared objects are similar).
func (c *Comparison) IsSame() bool {
return c.Removed.Empty() && c.Modified.Empty() && c.Added.Empty()
}
// String returns a human readable version of the comparison.
func (c *Comparison) String() string {
str := fmt.Sprintf("- Merged Object:\n%v\n", c.Merged.AsValue())
if !c.Modified.Empty() {
str += fmt.Sprintf("- Modified Fields:\n%v\n", c.Modified)
}
if !c.Added.Empty() {
str += fmt.Sprintf("- Added Fields:\n%v\n", c.Added)
}
if !c.Removed.Empty() {
str += fmt.Sprintf("- Removed Fields:\n%v\n", c.Removed)
}
return str
}

273
vendor/sigs.k8s.io/structured-merge-diff/typed/union.go generated vendored Normal file
View File

@@ -0,0 +1,273 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package typed
import (
"fmt"
"strings"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
func normalizeUnions(w *mergingWalker) error {
atom, found := w.schema.Resolve(w.typeRef)
if !found {
panic(fmt.Sprintf("Unable to resolve schema in normalize union: %v/%v", w.schema, w.typeRef))
}
// Unions can only be in structures, and the struct must not have been removed
if atom.Map == nil || w.out == nil {
return nil
}
old := &value.Map{}
if w.lhs != nil {
old = w.lhs.MapValue
}
for _, union := range atom.Map.Unions {
if err := newUnion(&union).Normalize(old, w.rhs.MapValue, w.out.MapValue); err != nil {
return err
}
}
return nil
}
func normalizeUnionsApply(w *mergingWalker) error {
atom, found := w.schema.Resolve(w.typeRef)
if !found {
panic(fmt.Sprintf("Unable to resolve schema in normalize union: %v/%v", w.schema, w.typeRef))
}
// Unions can only be in structures, and the struct must not have been removed
if atom.Map == nil || w.out == nil {
return nil
}
old := &value.Map{}
if w.lhs != nil {
old = w.lhs.MapValue
}
for _, union := range atom.Map.Unions {
if err := newUnion(&union).NormalizeApply(old, w.rhs.MapValue, w.out.MapValue); err != nil {
return err
}
}
return nil
}
type discriminated string
type field string
type discriminatedNames struct {
f2d map[field]discriminated
d2f map[discriminated]field
}
func newDiscriminatedName(f2d map[field]discriminated) discriminatedNames {
d2f := map[discriminated]field{}
for key, value := range f2d {
d2f[value] = key
}
return discriminatedNames{
f2d: f2d,
d2f: d2f,
}
}
func (dn discriminatedNames) toField(d discriminated) field {
if f, ok := dn.d2f[d]; ok {
return f
}
return field(d)
}
func (dn discriminatedNames) toDiscriminated(f field) discriminated {
if d, ok := dn.f2d[f]; ok {
return d
}
return discriminated(f)
}
type discriminator struct {
name string
}
func (d *discriminator) Set(m *value.Map, v discriminated) {
if d == nil {
return
}
m.Set(d.name, value.StringValue(string(v)))
}
func (d *discriminator) Get(m *value.Map) discriminated {
if d == nil || m == nil {
return ""
}
f, ok := m.Get(d.name)
if !ok {
return ""
}
if f.Value.StringValue == nil {
return ""
}
return discriminated(*f.Value.StringValue)
}
type fieldsSet map[field]struct{}
// newFieldsSet returns a map of the fields that are part of the union and are set
// in the given map.
func newFieldsSet(m *value.Map, fields []field) fieldsSet {
if m == nil {
return nil
}
set := fieldsSet{}
for _, f := range fields {
if subField, ok := m.Get(string(f)); ok && !subField.Value.Null {
set.Add(f)
}
}
return set
}
func (fs fieldsSet) Add(f field) {
if fs == nil {
fs = map[field]struct{}{}
}
fs[f] = struct{}{}
}
func (fs fieldsSet) One() *field {
for f := range fs {
return &f
}
return nil
}
func (fs fieldsSet) Has(f field) bool {
_, ok := fs[f]
return ok
}
func (fs fieldsSet) List() []field {
fields := []field{}
for f := range fs {
fields = append(fields, f)
}
return fields
}
func (fs fieldsSet) Difference(o fieldsSet) fieldsSet {
n := fieldsSet{}
for f := range fs {
if !o.Has(f) {
n.Add(f)
}
}
return n
}
func (fs fieldsSet) String() string {
s := []string{}
for k := range fs {
s = append(s, string(k))
}
return strings.Join(s, ", ")
}
type union struct {
deduceInvalidDiscriminator bool
d *discriminator
dn discriminatedNames
f []field
}
func newUnion(su *schema.Union) *union {
u := &union{}
if su.Discriminator != nil {
u.d = &discriminator{name: *su.Discriminator}
}
f2d := map[field]discriminated{}
for _, f := range su.Fields {
u.f = append(u.f, field(f.FieldName))
f2d[field(f.FieldName)] = discriminated(f.DiscriminatorValue)
}
u.dn = newDiscriminatedName(f2d)
u.deduceInvalidDiscriminator = su.DeduceInvalidDiscriminator
return u
}
// clear removes all the fields in map that are part of the union, but
// the one we decided to keep.
func (u *union) clear(m *value.Map, f field) {
for _, fieldName := range u.f {
if field(fieldName) != f {
m.Delete(string(fieldName))
}
}
}
func (u *union) Normalize(old, new, out *value.Map) error {
os := newFieldsSet(old, u.f)
ns := newFieldsSet(new, u.f)
diff := ns.Difference(os)
if u.d.Get(old) != u.d.Get(new) && u.d.Get(new) != "" {
if len(diff) == 1 && u.d.Get(new) != u.dn.toDiscriminated(*diff.One()) {
return fmt.Errorf("discriminator (%v) and field changed (%v) don't match", u.d.Get(new), diff.One())
}
if len(diff) > 1 {
return fmt.Errorf("multiple new fields added: %v", diff)
}
u.clear(out, u.dn.toField(u.d.Get(new)))
return nil
}
if len(ns) > 1 {
return fmt.Errorf("multiple fields set without discriminator change: %v", ns)
}
// Update discriminiator if it needs to be deduced.
if u.deduceInvalidDiscriminator && len(ns) == 1 {
u.d.Set(out, u.dn.toDiscriminated(*ns.One()))
}
return nil
}
func (u *union) NormalizeApply(applied, merged, out *value.Map) error {
as := newFieldsSet(applied, u.f)
if len(as) > 1 {
return fmt.Errorf("more than one field of union applied: %v", as)
}
if len(as) == 0 {
// None is set, just leave.
return nil
}
// We have exactly one, discriminiator must match if set
if u.d.Get(applied) != "" && u.d.Get(applied) != u.dn.toDiscriminated(*as.One()) {
return fmt.Errorf("applied discriminator (%v) doesn't match applied field (%v)", u.d.Get(applied), *as.One())
}
// Update discriminiator if needed
if u.deduceInvalidDiscriminator {
u.d.Set(out, u.dn.toDiscriminated(*as.One()))
}
// Clear others fields.
u.clear(out, *as.One())
return nil
}

View File

@@ -0,0 +1,235 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package typed
import (
"sync"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
var vPool = sync.Pool{
New: func() interface{} { return &validatingObjectWalker{} },
}
func (tv TypedValue) walker() *validatingObjectWalker {
v := vPool.Get().(*validatingObjectWalker)
v.value = tv.value
v.schema = tv.schema
v.typeRef = tv.typeRef
return v
}
func (v *validatingObjectWalker) finished() {
v.value = value.Value{}
v.schema = nil
v.typeRef = schema.TypeRef{}
v.leafFieldCallback = nil
v.nodeFieldCallback = nil
v.inLeaf = false
vPool.Put(v)
}
type validatingObjectWalker struct {
errorFormatter
value value.Value
schema *schema.Schema
typeRef schema.TypeRef
// If set, this is called on "leaf fields":
// * scalars: int/string/float/bool
// * atomic maps and lists
// * untyped fields
leafFieldCallback func(fieldpath.Path)
// If set, this is called on "node fields":
// * list items
// * map items
nodeFieldCallback func(fieldpath.Path)
// internal housekeeping--don't set when constructing.
inLeaf bool // Set to true if we're in a "big leaf"--atomic map/list
// Allocate only as many walkers as needed for the depth by storing them here.
spareWalkers *[]*validatingObjectWalker
}
func (v *validatingObjectWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef) *validatingObjectWalker {
if v.spareWalkers == nil {
// first descent.
v.spareWalkers = &[]*validatingObjectWalker{}
}
var v2 *validatingObjectWalker
if n := len(*v.spareWalkers); n > 0 {
v2, *v.spareWalkers = (*v.spareWalkers)[n-1], (*v.spareWalkers)[:n-1]
} else {
v2 = &validatingObjectWalker{}
}
*v2 = *v
v2.typeRef = tr
v2.errorFormatter.descend(pe)
return v2
}
func (v *validatingObjectWalker) finishDescent(v2 *validatingObjectWalker) {
// if the descent caused a realloc, ensure that we reuse the buffer
// for the next sibling.
v.errorFormatter = v2.errorFormatter.parent()
*v.spareWalkers = append(*v.spareWalkers, v2)
}
func (v *validatingObjectWalker) validate() ValidationErrors {
return resolveSchema(v.schema, v.typeRef, &v.value, v)
}
// doLeaf should be called on leaves before descending into children, if there
// will be a descent. It modifies v.inLeaf.
func (v *validatingObjectWalker) doLeaf() {
if v.inLeaf {
// We're in a "big leaf", an atomic map or list. Ignore
// subsequent leaves.
return
}
v.inLeaf = true
if v.leafFieldCallback != nil {
// At the moment, this is only used to build fieldsets; we can
// add more than the path in here if needed.
v.leafFieldCallback(v.path)
}
}
// doNode should be called on nodes after descending into children
func (v *validatingObjectWalker) doNode() {
if v.inLeaf {
// We're in a "big leaf", an atomic map or list. Ignore
// subsequent leaves.
return
}
if v.nodeFieldCallback != nil {
// At the moment, this is only used to build fieldsets; we can
// add more than the path in here if needed.
v.nodeFieldCallback(v.path)
}
}
func (v *validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
if errs := v.validateScalar(t, &v.value, ""); len(errs) > 0 {
return errs
}
// All scalars are leaf fields.
v.doLeaf()
return nil
}
func (v *validatingObjectWalker) visitListItems(t schema.List, list *value.List) (errs ValidationErrors) {
observedKeys := map[string]struct{}{}
for i, child := range list.Items {
pe, err := listItemToPathElement(t, i, child)
if err != nil {
errs = append(errs, v.errorf("element %v: %v", i, err.Error())...)
// If we can't construct the path element, we can't
// even report errors deeper in the schema, so bail on
// this element.
continue
}
keyStr := pe.String()
if _, found := observedKeys[keyStr]; found {
errs = append(errs, v.errorf("duplicate entries for key %v", keyStr)...)
}
observedKeys[keyStr] = struct{}{}
v2 := v.prepareDescent(pe, t.ElementType)
v2.value = child
errs = append(errs, v2.validate()...)
v2.doNode()
v.finishDescent(v2)
}
return errs
}
func (v *validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
list, err := listValue(v.value)
if err != nil {
return v.error(err)
}
if t.ElementRelationship == schema.Atomic {
v.doLeaf()
}
if list == nil {
return nil
}
errs = v.visitListItems(t, list)
return errs
}
func (v *validatingObjectWalker) visitMapItems(t schema.Map, m *value.Map) (errs ValidationErrors) {
fieldTypes := map[string]schema.TypeRef{}
for i := range t.Fields {
// I don't want to use the loop variable since a reference
// might outlive the loop iteration (in an error message).
f := t.Fields[i]
fieldTypes[f.Name] = f.Type
}
for i := range m.Items {
item := &m.Items[i]
pe := fieldpath.PathElement{FieldName: &item.Name}
if tr, ok := fieldTypes[item.Name]; ok {
v2 := v.prepareDescent(pe, tr)
v2.value = item.Value
errs = append(errs, v2.validate()...)
v.finishDescent(v2)
} else {
v2 := v.prepareDescent(pe, t.ElementType)
v2.value = item.Value
errs = append(errs, v2.validate()...)
v2.doNode()
v.finishDescent(v2)
}
}
return errs
}
func (v *validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
m, err := mapValue(v.value)
if err != nil {
return v.error(err)
}
if t.ElementRelationship == schema.Atomic {
v.doLeaf()
}
if m == nil {
return nil
}
errs = v.visitMapItems(t, m)
return errs
}

21
vendor/sigs.k8s.io/structured-merge-diff/value/doc.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
// Package value defines types for an in-memory representation of yaml or json
// objects, organized for convenient comparison with a schema (as defined by
// the sibling schema package). Functions for reading and writing the objects
// are also provided.
package value

View File

@@ -0,0 +1,149 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package value
import (
"bytes"
"fmt"
jsoniter "github.com/json-iterator/go"
)
var (
readPool = jsoniter.NewIterator(jsoniter.ConfigCompatibleWithStandardLibrary).Pool()
writePool = jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, nil, 1024).Pool()
)
// FromJSONFast is a helper function for reading a JSON document
func FromJSONFast(input []byte) (Value, error) {
iter := readPool.BorrowIterator(input)
defer readPool.ReturnIterator(iter)
return ReadJSONIter(iter)
}
func ReadJSONIter(iter *jsoniter.Iterator) (Value, error) {
next := iter.WhatIsNext()
switch next {
case jsoniter.InvalidValue:
iter.ReportError("reading an object", "got invalid token")
return Value{}, iter.Error
case jsoniter.StringValue:
str := String(iter.ReadString())
return Value{StringValue: &str}, nil
case jsoniter.NumberValue:
number := iter.ReadNumber()
isFloat := false
for _, c := range number {
if c == 'e' || c == 'E' || c == '.' {
isFloat = true
break
}
}
if isFloat {
f, err := number.Float64()
if err != nil {
iter.ReportError("parsing as float", err.Error())
return Value{}, err
}
return Value{FloatValue: (*Float)(&f)}, nil
}
i, err := number.Int64()
if err != nil {
iter.ReportError("parsing as float", err.Error())
return Value{}, err
}
return Value{IntValue: (*Int)(&i)}, nil
case jsoniter.NilValue:
iter.ReadNil()
return Value{Null: true}, nil
case jsoniter.BoolValue:
b := Boolean(iter.ReadBool())
return Value{BooleanValue: &b}, nil
case jsoniter.ArrayValue:
list := &List{}
iter.ReadArrayCB(func(iter *jsoniter.Iterator) bool {
v, err := ReadJSONIter(iter)
if err != nil {
iter.Error = err
return false
}
list.Items = append(list.Items, v)
return true
})
return Value{ListValue: list}, iter.Error
case jsoniter.ObjectValue:
m := &Map{}
iter.ReadObjectCB(func(iter *jsoniter.Iterator, key string) bool {
v, err := ReadJSONIter(iter)
if err != nil {
iter.Error = err
return false
}
m.Items = append(m.Items, Field{Name: key, Value: v})
return true
})
return Value{MapValue: m}, iter.Error
default:
return Value{}, fmt.Errorf("unexpected object type %v", next)
}
}
// ToJSONFast is a helper function for producing a JSon document.
func (v *Value) ToJSONFast() ([]byte, error) {
buf := bytes.Buffer{}
stream := writePool.BorrowStream(&buf)
defer writePool.ReturnStream(stream)
v.WriteJSONStream(stream)
err := stream.Flush()
return buf.Bytes(), err
}
func (v *Value) WriteJSONStream(stream *jsoniter.Stream) {
switch {
case v.Null:
stream.WriteNil()
case v.FloatValue != nil:
stream.WriteFloat64(float64(*v.FloatValue))
case v.IntValue != nil:
stream.WriteInt64(int64(*v.IntValue))
case v.BooleanValue != nil:
stream.WriteBool(bool(*v.BooleanValue))
case v.StringValue != nil:
stream.WriteString(string(*v.StringValue))
case v.ListValue != nil:
stream.WriteArrayStart()
for i := range v.ListValue.Items {
if i > 0 {
stream.WriteMore()
}
v.ListValue.Items[i].WriteJSONStream(stream)
}
stream.WriteArrayEnd()
case v.MapValue != nil:
stream.WriteObjectStart()
for i := range v.MapValue.Items {
if i > 0 {
stream.WriteMore()
}
stream.WriteObjectField(v.MapValue.Items[i].Name)
v.MapValue.Items[i].Value.WriteJSONStream(stream)
}
stream.WriteObjectEnd()
default:
stream.Write([]byte("invalid_value"))
}
}

View File

@@ -0,0 +1,234 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package value
import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v2"
)
// FromYAML is a helper function for reading a YAML document; it attempts to
// preserve order of keys within maps/structs. This is as a convenience to
// humans keeping YAML documents, not because there is a behavior difference.
//
// Known bug: objects with top-level arrays don't parse correctly.
func FromYAML(input []byte) (Value, error) {
var decoded interface{}
if len(input) == 4 && string(input) == "null" {
// Special case since the yaml package doesn't accurately
// preserve this.
return Value{Null: true}, nil
}
// This attempts to enable order sensitivity; note the yaml package is
// broken for documents that have root-level arrays, hence the two-step
// approach. TODO: This is a horrific hack. Is it worth it?
var ms yaml.MapSlice
if err := yaml.Unmarshal(input, &ms); err == nil {
decoded = ms
} else if err := yaml.Unmarshal(input, &decoded); err != nil {
return Value{}, err
}
v, err := FromUnstructured(decoded)
if err != nil {
return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input)
}
return v, nil
}
// FromJSON is a helper function for reading a JSON document
func FromJSON(input []byte) (Value, error) {
var decoded interface{}
if err := json.Unmarshal(input, &decoded); err != nil {
return Value{}, err
}
v, err := FromUnstructured(decoded)
if err != nil {
return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input)
}
return v, nil
}
// FromUnstructured will convert a go interface to a Value.
// It's most commonly expected to be used with map[string]interface{} as the
// input. `in` must not have any structures with cycles in them.
// yaml.MapSlice may be used for order-preservation.
func FromUnstructured(in interface{}) (Value, error) {
if in == nil {
return Value{Null: true}, nil
}
switch t := in.(type) {
case map[interface{}]interface{}:
m := Map{}
for rawKey, rawVal := range t {
k, ok := rawKey.(string)
if !ok {
return Value{}, fmt.Errorf("key %#v: not a string", k)
}
v, err := FromUnstructured(rawVal)
if err != nil {
return Value{}, fmt.Errorf("key %v: %v", k, err)
}
m.Set(k, v)
}
return Value{MapValue: &m}, nil
case map[string]interface{}:
m := Map{}
for k, rawVal := range t {
v, err := FromUnstructured(rawVal)
if err != nil {
return Value{}, fmt.Errorf("key %v: %v", k, err)
}
m.Set(k, v)
}
return Value{MapValue: &m}, nil
case yaml.MapSlice:
m := Map{}
for _, item := range t {
k, ok := item.Key.(string)
if !ok {
return Value{}, fmt.Errorf("key %#v is not a string", item.Key)
}
v, err := FromUnstructured(item.Value)
if err != nil {
return Value{}, fmt.Errorf("key %v: %v", k, err)
}
m.Set(k, v)
}
return Value{MapValue: &m}, nil
case []interface{}:
l := List{}
for i, rawVal := range t {
v, err := FromUnstructured(rawVal)
if err != nil {
return Value{}, fmt.Errorf("index %v: %v", i, err)
}
l.Items = append(l.Items, v)
}
return Value{ListValue: &l}, nil
case int:
n := Int(t)
return Value{IntValue: &n}, nil
case int8:
n := Int(t)
return Value{IntValue: &n}, nil
case int16:
n := Int(t)
return Value{IntValue: &n}, nil
case int32:
n := Int(t)
return Value{IntValue: &n}, nil
case int64:
n := Int(t)
return Value{IntValue: &n}, nil
case uint:
n := Int(t)
return Value{IntValue: &n}, nil
case uint8:
n := Int(t)
return Value{IntValue: &n}, nil
case uint16:
n := Int(t)
return Value{IntValue: &n}, nil
case uint32:
n := Int(t)
return Value{IntValue: &n}, nil
case float32:
f := Float(t)
return Value{FloatValue: &f}, nil
case float64:
f := Float(t)
return Value{FloatValue: &f}, nil
case string:
return StringValue(t), nil
case bool:
return BooleanValue(t), nil
default:
return Value{}, fmt.Errorf("type unimplemented: %t", in)
}
}
// ToYAML is a helper function for producing a YAML document; it attempts to
// preserve order of keys within maps/structs. This is as a convenience to
// humans keeping YAML documents, not because there is a behavior difference.
func (v *Value) ToYAML() ([]byte, error) {
return yaml.Marshal(v.ToUnstructured(true))
}
// ToJSON is a helper function for producing a JSon document.
func (v *Value) ToJSON() ([]byte, error) {
return json.Marshal(v.ToUnstructured(false))
}
// ToUnstructured will convert the Value into a go-typed object.
// If preserveOrder is true, then maps will be converted to the yaml.MapSlice
// type. Otherwise, map[string]interface{} must be used-- this destroys
// ordering information and is not recommended if the result of this will be
// serialized. Other types:
// * list -> []interface{}
// * others -> corresponding go type, wrapped in an interface{}
//
// Of note, floats and ints will always come out as float64 and int64,
// respectively.
func (v *Value) ToUnstructured(preserveOrder bool) interface{} {
switch {
case v.FloatValue != nil:
f := float64(*v.FloatValue)
return f
case v.IntValue != nil:
i := int64(*v.IntValue)
return i
case v.StringValue != nil:
return string(*v.StringValue)
case v.BooleanValue != nil:
return bool(*v.BooleanValue)
case v.ListValue != nil:
out := []interface{}{}
for _, item := range v.ListValue.Items {
out = append(out, item.ToUnstructured(preserveOrder))
}
return out
case v.MapValue != nil:
m := v.MapValue
if preserveOrder {
ms := make(yaml.MapSlice, len(m.Items))
for i := range m.Items {
ms[i] = yaml.MapItem{
Key: m.Items[i].Name,
Value: m.Items[i].Value.ToUnstructured(preserveOrder),
}
}
return ms
}
// This case is unavoidably lossy.
out := map[string]interface{}{}
for i := range m.Items {
out[m.Items[i].Name] = m.Items[i].Value.ToUnstructured(preserveOrder)
}
return out
default:
fallthrough
case v.Null == true:
return nil
}
}

361
vendor/sigs.k8s.io/structured-merge-diff/value/value.go generated vendored Normal file
View File

@@ -0,0 +1,361 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/
package value
import (
"fmt"
"sort"
"strings"
)
// A Value is an object; it corresponds to an 'atom' in the schema.
type Value struct {
// Exactly one of the below must be set.
FloatValue *Float
IntValue *Int
StringValue *String
BooleanValue *Boolean
ListValue *List
MapValue *Map
Null bool // represents an explicit `"foo" = null`
}
// Equals returns true iff the two values are equal.
func (v Value) Equals(rhs Value) bool {
return !v.Less(rhs) && !rhs.Less(v)
}
// Less provides a total ordering for Value (so that they can be sorted, even
// if they are of different types).
func (v Value) Less(rhs Value) bool {
if v.FloatValue != nil {
if rhs.FloatValue == nil {
// Extra: compare floats and ints numerically.
if rhs.IntValue != nil {
return float64(*v.FloatValue) < float64(*rhs.IntValue)
}
return true
}
return *v.FloatValue < *rhs.FloatValue
} else if rhs.FloatValue != nil {
// Extra: compare floats and ints numerically.
if v.IntValue != nil {
return float64(*v.IntValue) < float64(*rhs.FloatValue)
}
return false
}
if v.IntValue != nil {
if rhs.IntValue == nil {
return true
}
return *v.IntValue < *rhs.IntValue
} else if rhs.IntValue != nil {
return false
}
if v.StringValue != nil {
if rhs.StringValue == nil {
return true
}
return *v.StringValue < *rhs.StringValue
} else if rhs.StringValue != nil {
return false
}
if v.BooleanValue != nil {
if rhs.BooleanValue == nil {
return true
}
if *v.BooleanValue == *rhs.BooleanValue {
return false
}
return *v.BooleanValue == false
} else if rhs.BooleanValue != nil {
return false
}
if v.ListValue != nil {
if rhs.ListValue == nil {
return true
}
return v.ListValue.Less(rhs.ListValue)
} else if rhs.ListValue != nil {
return false
}
if v.MapValue != nil {
if rhs.MapValue == nil {
return true
}
return v.MapValue.Less(rhs.MapValue)
} else if rhs.MapValue != nil {
return false
}
if v.Null {
if !rhs.Null {
return true
}
return false
} else if rhs.Null {
return false
}
// Invalid Value-- nothing is set.
return false
}
type Int int64
type Float float64
type String string
type Boolean bool
// Field is an individual key-value pair.
type Field struct {
Name string
Value Value
}
// List is a list of items.
type List struct {
Items []Value
}
// Less compares two lists lexically.
func (l *List) Less(rhs *List) bool {
i := 0
for {
if i >= len(l.Items) && i >= len(rhs.Items) {
// Lists are the same length and all items are equal.
return false
}
if i >= len(l.Items) {
// LHS is shorter.
return true
}
if i >= len(rhs.Items) {
// RHS is shorter.
return false
}
if l.Items[i].Less(rhs.Items[i]) {
// LHS is less; return
return true
}
if rhs.Items[i].Less(l.Items[i]) {
// RHS is less; return
return false
}
// The items are equal; continue.
i++
}
}
// Map is a map of key-value pairs. It represents both structs and maps. We use
// a list and a go-language map to preserve order.
//
// Set and Get helpers are provided.
type Map struct {
Items []Field
// may be nil; lazily constructed.
// TODO: Direct modifications to Items above will cause serious problems.
index map[string]int
// may be empty; lazily constructed.
// TODO: Direct modifications to Items above will cause serious problems.
order []int
}
func (m *Map) computeOrder() []int {
if len(m.order) != len(m.Items) {
m.order = make([]int, len(m.Items))
for i := range m.order {
m.order[i] = i
}
sort.SliceStable(m.order, func(i, j int) bool {
return m.Items[m.order[i]].Name < m.Items[m.order[j]].Name
})
}
return m.order
}
// Less compares two maps lexically.
func (m *Map) Less(rhs *Map) bool {
var noAllocL, noAllocR [2]int
var morder, rorder []int
// For very short maps (<2 elements) this permits us to avoid
// allocating the order array. We could make this accomodate larger
// maps, but 2 items should be enough to cover most path element
// comparisons, and at some point there will be diminishing returns.
// This has a large effect on the path element deserialization test,
// because everything is sorted / compared, but only once.
switch len(m.Items) {
case 0:
morder = noAllocL[0:0]
case 1:
morder = noAllocL[0:1]
case 2:
morder = noAllocL[0:2]
if m.Items[0].Name > m.Items[1].Name {
morder[0] = 1
} else {
morder[1] = 1
}
default:
morder = m.computeOrder()
}
switch len(rhs.Items) {
case 0:
rorder = noAllocR[0:0]
case 1:
rorder = noAllocR[0:1]
case 2:
rorder = noAllocR[0:2]
if rhs.Items[0].Name > rhs.Items[1].Name {
rorder[0] = 1
} else {
rorder[1] = 1
}
default:
rorder = rhs.computeOrder()
}
i := 0
for {
if i >= len(morder) && i >= len(rorder) {
// Maps are the same length and all items are equal.
return false
}
if i >= len(morder) {
// LHS is shorter.
return true
}
if i >= len(rorder) {
// RHS is shorter.
return false
}
fa, fb := &m.Items[morder[i]], &rhs.Items[rorder[i]]
if fa.Name != fb.Name {
// the map having the field name that sorts lexically less is "less"
return fa.Name < fb.Name
}
if fa.Value.Less(fb.Value) {
// LHS is less; return
return true
}
if fb.Value.Less(fa.Value) {
// RHS is less; return
return false
}
// The items are equal; continue.
i++
}
}
// Get returns the (Field, true) or (nil, false) if it is not present
func (m *Map) Get(key string) (*Field, bool) {
if m.index == nil {
m.index = map[string]int{}
for i := range m.Items {
m.index[m.Items[i].Name] = i
}
}
f, ok := m.index[key]
if !ok {
return nil, false
}
return &m.Items[f], true
}
// Set inserts or updates the given item.
func (m *Map) Set(key string, value Value) {
if f, ok := m.Get(key); ok {
f.Value = value
return
}
m.Items = append(m.Items, Field{Name: key, Value: value})
i := len(m.Items) - 1
m.index[key] = i
m.order = nil
}
// Delete removes the key from the set.
func (m *Map) Delete(key string) {
items := []Field{}
for i := range m.Items {
if m.Items[i].Name != key {
items = append(items, m.Items[i])
}
}
m.Items = items
m.index = nil // Since the list has changed
m.order = nil
}
// StringValue returns s as a scalar string Value.
func StringValue(s string) Value {
s2 := String(s)
return Value{StringValue: &s2}
}
// IntValue returns i as a scalar numeric (integer) Value.
func IntValue(i int) Value {
i2 := Int(i)
return Value{IntValue: &i2}
}
// FloatValue returns f as a scalar numeric (float) Value.
func FloatValue(f float64) Value {
f2 := Float(f)
return Value{FloatValue: &f2}
}
// BooleanValue returns b as a scalar boolean Value.
func BooleanValue(b bool) Value {
b2 := Boolean(b)
return Value{BooleanValue: &b2}
}
// String returns a human-readable representation of the value.
func (v Value) String() string {
switch {
case v.FloatValue != nil:
return fmt.Sprintf("%v", *v.FloatValue)
case v.IntValue != nil:
return fmt.Sprintf("%v", *v.IntValue)
case v.StringValue != nil:
return fmt.Sprintf("%q", *v.StringValue)
case v.BooleanValue != nil:
return fmt.Sprintf("%v", *v.BooleanValue)
case v.ListValue != nil:
strs := []string{}
for _, item := range v.ListValue.Items {
strs = append(strs, item.String())
}
return "[" + strings.Join(strs, ",") + "]"
case v.MapValue != nil:
strs := []string{}
for _, i := range v.MapValue.Items {
strs = append(strs, fmt.Sprintf("%v=%v", i.Name, i.Value))
}
return "{" + strings.Join(strs, ";") + "}"
default:
fallthrough
case v.Null == true:
return "null"
}
}