diff --git a/vendor/sigs.k8s.io/structured-merge-diff/LICENSE b/vendor/sigs.k8s.io/structured-merge-diff/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/LICENSE @@ -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. diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/BUILD new file mode 100644 index 00000000000..db1aff0f8a2 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/BUILD @@ -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"], +) diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/doc.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/doc.go new file mode 100644 index 00000000000..f4fbbff262e --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/doc.go @@ -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 diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/element.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/element.go new file mode 100644 index 00000000000..fb706a662b4 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/element.go @@ -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) + } +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/fromvalue.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/fromvalue.go new file mode 100644 index 00000000000..a4b054fc7aa --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/fromvalue.go @@ -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} +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/managers.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/managers.go new file mode 100644 index 00000000000..a6adb0e0168 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/managers.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go new file mode 100644 index 00000000000..8c2225445d6 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go new file mode 100644 index 00000000000..91e5dda1060 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go @@ -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) + } +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/merge/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/merge/BUILD new file mode 100644 index 00000000000..498b255b8f7 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/merge/BUILD @@ -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"], +) diff --git a/vendor/sigs.k8s.io/structured-merge-diff/merge/conflict.go b/vendor/sigs.k8s.io/structured-merge-diff/merge/conflict.go new file mode 100644 index 00000000000..1056037b83c --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/merge/conflict.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go b/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go new file mode 100644 index 00000000000..3e7ce935728 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD new file mode 100644 index 00000000000..65f9085e16b --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD @@ -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"], +) diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/doc.go b/vendor/sigs.k8s.io/structured-merge-diff/schema/doc.go new file mode 100644 index 00000000000..9081ccbc73c --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/doc.go @@ -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 diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go b/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go new file mode 100644 index 00000000000..a30bab883e8 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/schemaschema.go b/vendor/sigs.k8s.io/structured-merge-diff/schema/schemaschema.go new file mode 100644 index 00000000000..628c5f861bf --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/schemaschema.go @@ -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 +` diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD new file mode 100644 index 00000000000..1e6481fb1e4 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD @@ -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"], +) diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/doc.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/doc.go new file mode 100644 index 00000000000..ca4e60542af --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/doc.go @@ -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 diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go new file mode 100644 index 00000000000..95b343014d0 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/merge.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/merge.go new file mode 100644 index 00000000000..d2bbe7b08dc --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/merge.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go new file mode 100644 index 00000000000..de1585d6a60 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go @@ -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) +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go new file mode 100644 index 00000000000..3a7ee0ceb97 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go new file mode 100644 index 00000000000..38cb66c7174 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go @@ -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 +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/value/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/value/BUILD new file mode 100644 index 00000000000..b01bb7dae19 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/value/BUILD @@ -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"], +) diff --git a/vendor/sigs.k8s.io/structured-merge-diff/value/doc.go b/vendor/sigs.k8s.io/structured-merge-diff/value/doc.go new file mode 100644 index 00000000000..84d7f0f3fc2 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/value/doc.go @@ -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 diff --git a/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go b/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go new file mode 100644 index 00000000000..62660f2fca4 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go @@ -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 + } +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/value/value.go b/vendor/sigs.k8s.io/structured-merge-diff/value/value.go new file mode 100644 index 00000000000..a5dbc5f4711 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/value/value.go @@ -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" + } +}