Add structured-merge-diff vendor

This is where most of the logic actually lives.
pull/564/head
Antoine Pelisse 2019-01-16 21:11:43 -08:00
parent 732cb10019
commit 5869ce6dfe
26 changed files with 3345 additions and 0 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,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"element.go",
"fromvalue.go",
"managers.go",
"path.go",
"set.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/fieldpath",
importpath = "sigs.k8s.io/structured-merge-diff/fieldpath",
visibility = ["//visibility:public"],
deps = ["//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

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,184 @@
/*
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.Field
// 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
}
// String presents the path element as a human-readable string.
func (e PathElement) String() string {
switch {
case e.FieldName != nil:
return "." + *e.FieldName
case len(e.Key) > 0:
strs := make([]string, len(e.Key))
for i, k := range e.Key {
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 {
// The strange construction is because there's no way to test
// PathElements for equality (it can't be used as a key for a map).
members map[string]PathElement
}
// Insert adds pe to the set.
func (s *PathElementSet) Insert(pe PathElement) {
serialized := pe.String()
if s.members == nil {
s.members = map[string]PathElement{
serialized: pe,
}
return
}
if _, ok := s.members[serialized]; !ok {
s.members[serialized] = pe
}
}
// Union returns a set containing elements that appear in either s or s2.
func (s *PathElementSet) Union(s2 *PathElementSet) *PathElementSet {
out := &PathElementSet{
members: map[string]PathElement{},
}
for k, v := range s.members {
out.members[k] = v
}
for k, v := range s2.members {
out.members[k] = v
}
return out
}
// Intersection returns a set containing elements which appear in both s and s2.
func (s *PathElementSet) Intersection(s2 *PathElementSet) *PathElementSet {
out := &PathElementSet{
members: map[string]PathElement{},
}
for k, v := range s.members {
if _, ok := s2.members[k]; ok {
out.members[k] = v
}
}
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{
members: map[string]PathElement{},
}
for k, v := range s.members {
if _, ok := s2.members[k]; !ok {
out.members[k] = v
}
}
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 {
if s.members == nil {
return false
}
_, ok := s.members[pe.String()]
return ok
}
// 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 _, ok := s2.members[k]; !ok {
return false
}
}
return true
}
// Iterate calls f for each PathElement in the set.
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: keys}
}
return PathElement{Index: &index}
}

View File

@ -0,0 +1,73 @@
/*
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
// VersionedSet associates a version to a set.
type VersionedSet struct {
*Set
APIVersion APIVersion
}
// 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.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.Difference(right.Set).Union(right.Difference(left.Set))
if !newSet.Empty() {
diff[manager] = &VersionedSet{
Set: newSet,
APIVersion: right.APIVersion,
}
}
}
for manager, set := range rhs {
if _, ok := lhs[manager]; ok {
// Already done
continue
}
if !set.Empty() {
diff[manager] = set
}
}
return diff
}

View File

@ -0,0 +1,75 @@
/*
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, "")
}
// 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: 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,305 @@
/*
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 (
"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
// * and are not children of elements that 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)
}
// 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 map[string]setNode
}
// Descend adds pe to the set if necessary, returning the associated subset.
func (s *SetNodeMap) Descend(pe PathElement) *Set {
serialized := pe.String()
if s.members == nil {
s.members = map[string]setNode{}
}
if n, ok := s.members[serialized]; ok {
return n.set
}
ss := &Set{}
s.members[serialized] = setNode{
pathElement: pe,
set: ss,
}
return ss
}
// 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) {
if s.members == nil {
return nil, false
}
serialized := pe.String()
if n, ok := s.members[serialized]; ok {
return n.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 k, v := range s.members {
v2, ok := s2.members[k]
if !ok {
return false
}
if !v.set.Equals(v2.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{}
for k, sn := range s.members {
pe := sn.pathElement
if sn2, ok := s2.members[k]; ok {
*out.Descend(pe) = *sn.set.Union(sn2.set)
} else {
*out.Descend(pe) = *sn.set
}
}
for k, sn2 := range s2.members {
pe := sn2.pathElement
if _, ok := s.members[k]; ok {
// already handled
continue
}
*out.Descend(pe) = *sn2.set
}
return out
}
// Intersection returns a SetNodeMap with members that appear in both s and s2.
func (s *SetNodeMap) Intersection(s2 *SetNodeMap) *SetNodeMap {
out := &SetNodeMap{}
for k, sn := range s.members {
pe := sn.pathElement
if sn2, ok := s2.members[k]; ok {
i := *sn.set.Intersection(sn2.set)
if !i.Empty() {
*out.Descend(pe) = i
}
}
}
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{}
for k, sn := range s.members {
pe := sn.pathElement
if s2.Members.Has(pe) {
continue
}
if sn2, ok := s2.Children.members[k]; ok {
diff := *sn.set.Difference(sn2.set)
// We aren't permitted to add nodes with no elements.
if !diff.Empty() {
*out.Descend(pe) = diff
}
} else {
*out.Descend(pe) = *sn.set
}
}
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)
}
}

30
vendor/sigs.k8s.io/structured-merge-diff/merge/BUILD generated vendored Normal file
View File

@ -0,0 +1,30 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"conflict.go",
"update.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/merge",
importpath = "sigs.k8s.io/structured-merge-diff/merge",
visibility = ["//visibility:public"],
deps = [
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/typed:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,91 @@
/*
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)
}
// 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")
}
// ConflictsFromManagers creates a list of conflicts given Managers sets.
func ConflictsFromManagers(sets fieldpath.ManagedFields) Conflicts {
conflicts := []Conflict{}
for manager, set := range sets {
set.Iterate(func(p fieldpath.Path) {
conflicts = append(conflicts, Conflict{
Manager: manager,
Path: p,
})
})
}
return conflicts
}

View File

@ -0,0 +1,142 @@
/*
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)
}
// Updater is the object used to compute updated FieldSets and also
// merge the object on Apply.
type Updater struct {
Converter Converter
}
func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
if managers == nil {
managers = fieldpath.ManagedFields{}
}
conflicts := fieldpath.ManagedFields{}
type Versioned struct {
oldObject typed.TypedValue
newObject typed.TypedValue
}
versions := map[fieldpath.APIVersion]Versioned{
version: Versioned{
oldObject: oldObject,
newObject: newObject,
},
}
for manager, managerSet := range managers {
if manager == workflow {
continue
}
versioned, ok := versions[managerSet.APIVersion]
if !ok {
var err error
versioned.oldObject, err = s.Converter.Convert(oldObject, managerSet.APIVersion)
if err != nil {
return nil, fmt.Errorf("failed to convert old object: %v", err)
}
versioned.newObject, err = s.Converter.Convert(newObject, managerSet.APIVersion)
if err != nil {
return nil, fmt.Errorf("failed to convert new object: %v", err)
}
versions[managerSet.APIVersion] = versioned
}
compare, err := versioned.oldObject.Compare(versioned.newObject)
if err != nil {
return nil, fmt.Errorf("failed to compare objects: %v", err)
}
conflictSet := managerSet.Intersection(compare.Modified.Union(compare.Added))
if !conflictSet.Empty() {
conflicts[manager] = &fieldpath.VersionedSet{
Set: conflictSet,
APIVersion: managerSet.APIVersion,
}
}
}
if !force && len(conflicts) != 0 {
return nil, ConflictsFromManagers(conflicts)
}
for manager, conflictSet := range conflicts {
managers[manager].Set = managers[manager].Set.Difference(conflictSet.Set)
}
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) (fieldpath.ManagedFields, error) {
var err error
managers, err = s.update(liveObject, newObject, version, managers, manager, true)
if err != nil {
return fieldpath.ManagedFields{}, err
}
compare, err := liveObject.Compare(newObject)
if err != nil {
return fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
}
if _, ok := managers[manager]; !ok {
managers[manager] = &fieldpath.VersionedSet{
Set: fieldpath.NewSet(),
}
}
managers[manager].Set = managers[manager].Set.Union(compare.Modified).Union(compare.Added).Difference(compare.Removed)
managers[manager].APIVersion = version
return 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) {
newObject, err := liveObject.Merge(configObject)
if err != nil {
return typed.TypedValue{}, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
}
managers, err = s.update(liveObject, newObject, version, managers, manager, force)
if err != nil {
return typed.TypedValue{}, fieldpath.ManagedFields{}, err
}
// TODO: Remove unconflicting removed fields
set, err := configObject.ToFieldSet()
if err != nil {
return typed.TypedValue{}, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
}
managers[manager] = &fieldpath.VersionedSet{
Set: set,
APIVersion: version,
}
return newObject, managers, nil
}

27
vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD generated vendored Normal file
View File

@ -0,0 +1,27 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"elements.go",
"schemaschema.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/schema",
importpath = "sigs.k8s.io/structured-merge-diff/schema",
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

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,219 @@
/*
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.
type Atom struct {
// Exactly one of the below must be set.
*Scalar `yaml:"scalar,omitempty"`
*Struct `yaml:"struct,omitempty"`
*List `yaml:"list,omitempty"`
*Map `yaml:"map,omitempty"`
*Untyped `yaml:"untyped,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, structs, untyped).
type ElementRelationship string
const (
// Associative only applies to lists (see the documentation there).
Associative = ElementRelationship("associative")
// Atomic makes container types (lists, maps, structs, untyped) behave
// as scalars / leaf fields (which is the default for untyped data).
Atomic = ElementRelationship("atomic")
// Separable means the items of the container type have no particular
// relationship (default behavior for maps and structs).
Separable = ElementRelationship("separable")
)
// Struct represents a type which is composed of a number of different fields.
// Each field has a name and a type.
//
// TODO: in the future, we will add one-of groups (sometimes called unions).
type Struct 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"`
// TODO: Implement unions, either this way or by inlining.
// Unions are groupings of fields with special rules. They 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.
// Unions []Union `yaml:"unions,omitempty"`
// ElementRelationship states the relationship between the struct'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 structs is `separable`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,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 struct, the list is treated as a map.
// - The list element must not be a map or a list itself.
// 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
// struct, then Keys must have non-zero length, and it lists the fields
// of the element's struct 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"`
}
// 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.
//
// Although serialized the same, maps are different from structs in that each
// map item must have the same type.
//
// 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).
type Map struct {
// ElementType is the type of the list's elements.
ElementType TypeRef `yaml:"elementType,omitempty"`
// ElementRelationship states the relationship between the map's items.
// * `separable` 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.
// TODO: find a simple example.
// The default behavior for maps is `separable`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
}
// Untyped represents types that allow arbitrary content. (Think: plugin
// objects.)
type Untyped struct {
// ElementRelationship states the relationship between the items, if
// container-typed data happens to be present here.
// * `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.
// TODO: support "guess" (guesses at associative list keys)
// TODO: support "lookup" (calls a lookup function to figure out the
// schema based on the data)
// The default behavior for untyped data is `atomic`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,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,128 @@
/*
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
struct:
fields:
- name: types
type:
list:
elementRelationship: associative
elementType:
namedType: typeDef
keys:
- name
- name: typeDef
struct:
fields:
- name: name
type:
scalar: string
- name: scalar
type:
scalar: string
- name: struct
type:
namedType: struct
- name: list
type:
namedType: list
- name: map
type:
namedType: map
- name: untyped
type:
namedType: untyped
- name: typeRef
struct:
fields:
- name: namedType
type:
scalar: string
- name: scalar
type:
scalar: string
- name: struct
type:
namedType: struct
- name: list
type:
namedType: list
- name: map
type:
namedType: map
- name: untyped
type:
namedType: untyped
- name: scalar
scalar: string
- name: struct
struct:
fields:
- name: fields
type:
list:
elementType:
namedType: structField
elementRelationship: associative
keys: [ "name" ]
- name: elementRelationship
type:
scalar: string
- name: structField
struct:
fields:
- name: name
type:
scalar: string
- name: type
type:
namedType: typeRef
- name: list
struct:
fields:
- name: elementType
type:
namedType: typeRef
- name: elementRelationship
type:
scalar: string
- name: keys
type:
list:
elementType:
scalar: string
- name: map
struct:
fields:
- name: elementType
type:
namedType: typeRef
- name: elementRelationship
type:
scalar: string
- name: untyped
struct:
fields:
- name: elementRelationship
type:
scalar: string
`

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

@ -0,0 +1,36 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"helpers.go",
"merge.go",
"parser.go",
"typed.go",
"validate.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/typed",
importpath = "sigs.k8s.io/structured-merge-diff/typed",
visibility = ["//visibility:public"],
deps = [
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/schema:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

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,246 @@
/*
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)
}
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
doStruct(schema.Struct) ValidationErrors
doList(schema.List) ValidationErrors
doMap(schema.Map) ValidationErrors
doUntyped(schema.Untyped) ValidationErrors
errorf(msg string, args ...interface{}) ValidationErrors
}
func resolveSchema(s *schema.Schema, tr schema.TypeRef, ah atomHandler) ValidationErrors {
a, ok := s.Resolve(tr)
if !ok {
return ah.errorf("schema error: no type found matching: %v", *tr.NamedType)
}
switch {
case a.Scalar != nil:
return ah.doScalar(*a.Scalar)
case a.Struct != nil:
return ah.doStruct(*a.Struct)
case a.List != nil:
return ah.doList(*a.List)
case a.Map != nil:
return ah.doMap(*a.Map)
case a.Untyped != nil:
return ah.doUntyped(*a.Untyped)
}
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
}
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 mapOrStructValue(val value.Value, typeName string) (*value.Map, error) {
switch {
case val.Null:
return nil, nil
case val.MapValue != nil:
return val.MapValue, nil
default:
return nil, fmt.Errorf("expected %v, got %v", typeName, val)
}
}
func (ef errorFormatter) rejectExtraStructFields(m *value.Map, allowedNames map[string]struct{}, prefix string) (errs ValidationErrors) {
if m == nil {
return nil
}
for _, f := range m.Items {
if _, allowed := allowedNames[f.Name]; !allowed {
errs = append(errs, ef.errorf("%vfield %v is not mentioned in the schema", prefix, f.Name)...)
}
}
return errs
}
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")
}
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)
}
pe.Key = append(pe.Key, value.Field{
Name: fieldName,
Value: fieldValue,
})
}
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
}

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

@ -0,0 +1,410 @@
/*
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
}
// 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() 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")
}
errs := resolveSchema(w.schema, 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 {
w2 := *w
w2.typeRef = tr
w2.errorFormatter.descend(pe)
w2.lhs = nil
w2.rhs = nil
w2.out = nil
return &w2
}
func (w *mergingWalker) visitStructFields(t schema.Struct, lhs, rhs *value.Map) (errs ValidationErrors) {
out := &value.Map{}
valOrNil := func(m *value.Map, name string) *value.Value {
if m == nil {
return nil
}
val, ok := m.Get(name)
if ok {
return &val.Value
}
return nil
}
allowedNames := map[string]struct{}{}
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]
allowedNames[f.Name] = struct{}{}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &f.Name}, f.Type)
w2.lhs = valOrNil(lhs, f.Name)
w2.rhs = valOrNil(rhs, f.Name)
if w2.lhs == nil && w2.rhs == nil {
// All fields are optional
continue
}
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Set(f.Name, *w2.out)
}
}
// All fields may be optional, but unknown fields are not allowed.
errs = append(errs, w.rejectExtraStructFields(lhs, allowedNames, "lhs: ")...)
errs = append(errs, w.rejectExtraStructFields(rhs, allowedNames, "rhs: ")...)
if len(errs) > 0 {
return errs
}
if len(out.Items) > 0 {
w.out = &value.Value{MapValue: out}
}
return errs
}
func (w *mergingWalker) derefMapOrStruct(prefix, typeName 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 := mapOrStructValue(*v, typeName)
if err != nil {
return w.prefixError(prefix, err)
}
*dest = m
return nil
}
func (w *mergingWalker) doStruct(t schema.Struct) (errs ValidationErrors) {
var lhs, rhs *value.Map
errs = append(errs, w.derefMapOrStruct("lhs: ", "struct", w.lhs, &lhs)...)
errs = append(errs, w.derefMapOrStruct("rhs: ", "struct", w.rhs, &rhs)...)
if len(errs) > 0 {
return errs
}
// 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 {
// nil is a valid map!
return nil
}
errs = w.visitStructFields(t, lhs, rhs)
// TODO: Check unions.
return errs
}
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)
}
// 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)
}
}
}
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
errs = append(errs, w.derefList("lhs: ", w.lhs, &lhs)...)
errs = append(errs, w.derefList("rhs: ", w.rhs, &rhs)...)
if len(errs) > 0 {
return errs
}
// 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{}
if lhs != nil {
for _, litem := range lhs.Items {
name := litem.Name
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, t.ElementType)
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.Set(name, *w2.out)
}
}
}
if rhs != nil {
for _, ritem := range rhs.Items {
if lhs != nil {
if _, ok := lhs.Get(ritem.Name); ok {
continue
}
}
name := ritem.Name
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, t.ElementType)
w2.rhs = &ritem.Value
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Set(name, *w2.out)
}
}
}
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
errs = append(errs, w.derefMapOrStruct("lhs: ", "map", w.lhs, &lhs)...)
errs = append(errs, w.derefMapOrStruct("rhs: ", "map", w.rhs, &rhs)...)
if len(errs) > 0 {
return errs
}
// 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.visitMapItems(t, lhs, rhs)
return errs
}
func (w *mergingWalker) doUntyped(t schema.Untyped) (errs ValidationErrors) {
if t.ElementRelationship == "" || t.ElementRelationship == schema.Atomic {
// Untyped sections allow anything, and are considered leaf
// fields.
w.doLeaf()
}
return nil
}

View File

@ -0,0 +1,114 @@
/*
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(schema YAMLObject) (*Parser, error) {
p := Parser{}
err := yaml.Unmarshal([]byte(schema), &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)
}
return create(schema)
}
// 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{
parser: p,
typename: name,
}
}
// ParseableType allows for easy production of typed objects.
type ParseableType struct {
parser *Parser
typename string
}
// IsValid return true if p's schema and typename are valid.
func (p *ParseableType) IsValid() bool {
_, ok := p.parser.Schema.Resolve(schema.TypeRef{NamedType: &p.typename})
return ok
}
// New returns a new empty object with the current schema and the
// type "typename".
func (p *ParseableType) New() (TypedValue, error) {
return p.FromYAML(YAMLObject("{}"))
}
// 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 TypedValue{}, err
}
return AsTyped(v, &p.parser.Schema, p.typename)
}
// 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 TypedValue{}, err
}
return AsTyped(v, &p.parser.Schema, p.typename)
}

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

@ -0,0 +1,213 @@
/*
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"
"reflect"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
// TypedValue is a value of some specific type.
type TypedValue struct {
value value.Value
typeRef schema.TypeRef
schema *schema.Schema
}
// 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, typeName string) (TypedValue, error) {
tv := TypedValue{
value: v,
typeRef: schema.TypeRef{NamedType: &typeName},
schema: s,
}
if err := tv.Validate(); err != nil {
return TypedValue{}, err
}
return tv, nil
}
// 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 {
if errs := tv.walker().validate(); len(errs) != 0 {
return errs
}
return nil
}
// ToFieldSet creates a set containing every leaf field mentioned in tv, or
// validation errors, if any were encountered.
func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) {
s := fieldpath.NewSet()
w := tv.walker()
w.leafFieldCallback = 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)
}
// 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
}
// 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 !reflect.DeepEqual(w.rhs, w.lhs) {
// TODO: reflect.DeepEqual 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
}
func merge(lhs, rhs TypedValue, rule, postRule mergeRule) (TypedValue, error) {
if lhs.schema != rhs.schema {
return TypedValue{}, errorFormatter{}.
errorf("expected objects with types from the same schema")
}
if !reflect.DeepEqual(lhs.typeRef, rhs.typeRef) {
return TypedValue{}, errorFormatter{}.
errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
}
mw := mergingWalker{
lhs: &lhs.value,
rhs: &rhs.value,
schema: lhs.schema,
typeRef: lhs.typeRef,
rule: rule,
postItemHook: postRule,
}
errs := mw.merge()
if len(errs) > 0 {
return TypedValue{}, 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
}
// AsTypeUnvalidated is just like WithType, 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, typeName string) TypedValue {
tv := TypedValue{
value: v,
typeRef: schema.TypeRef{NamedType: &typeName},
schema: s,
}
return tv
}

View File

@ -0,0 +1,208 @@
/*
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"
)
func (tv TypedValue) walker() *validatingObjectWalker {
return &validatingObjectWalker{
value: tv.value,
schema: tv.schema,
typeRef: tv.typeRef,
}
}
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)
// internal housekeeping--don't set when constructing.
inLeaf bool // Set to true if we're in a "big leaf"--atomic map/list
}
func (v validatingObjectWalker) validate() ValidationErrors {
return resolveSchema(v.schema, v.typeRef, 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)
}
}
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) visitStructFields(t schema.Struct, m *value.Map) (errs ValidationErrors) {
allowedNames := map[string]struct{}{}
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]
allowedNames[f.Name] = struct{}{}
child, ok := m.Get(f.Name)
if !ok {
// All fields are optional
continue
}
v2 := v
v2.errorFormatter.descend(fieldpath.PathElement{FieldName: &f.Name})
v2.value = child.Value
v2.typeRef = f.Type
errs = append(errs, v2.validate()...)
}
// All fields may be optional, but unknown fields are not allowed.
return append(errs, v.rejectExtraStructFields(m, allowedNames, "")...)
}
func (v validatingObjectWalker) doStruct(t schema.Struct) (errs ValidationErrors) {
m, err := mapOrStructValue(v.value, "struct")
if err != nil {
return v.error(err)
}
if t.ElementRelationship == schema.Atomic {
v.doLeaf()
}
if m == nil {
// nil is a valid map!
return nil
}
errs = v.visitStructFields(t, m)
// TODO: Check unions.
return errs
}
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
v2.errorFormatter.descend(pe)
v2.value = child
v2.typeRef = t.ElementType
errs = append(errs, v2.validate()...)
}
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) {
for _, item := range m.Items {
v2 := v
name := item.Name
v2.errorFormatter.descend(fieldpath.PathElement{FieldName: &name})
v2.value = item.Value
v2.typeRef = t.ElementType
errs = append(errs, v2.validate()...)
}
return errs
}
func (v validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
m, err := mapOrStructValue(v.value, "map")
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
}
func (v validatingObjectWalker) doUntyped(t schema.Untyped) (errs ValidationErrors) {
if t.ElementRelationship == "" || t.ElementRelationship == schema.Atomic {
// Untyped sections allow anything, and are considered leaf
// fields.
v.doLeaf()
}
return nil
}

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

@ -0,0 +1,28 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"unstructured.go",
"value.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/value",
importpath = "sigs.k8s.io/structured-merge-diff/value",
visibility = ["//visibility:public"],
deps = ["//vendor/gopkg.in/yaml.v2:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

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,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) == 0 || (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
}
}

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

@ -0,0 +1,139 @@
/*
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"
"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`
}
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
}
// 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]*Field
}
// 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]*Field{}
for i := range m.Items {
f := &m.Items[i]
m.index[f.Name] = f
}
}
f, ok := m.index[key]
return f, ok
}
// 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})
m.index = nil // Since the append might have reallocated
}
// 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"
}
}