From 5869ce6dfef1f418dd3a7fa0a5d14cec128deb4a Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Wed, 16 Jan 2019 21:11:43 -0800 Subject: [PATCH 01/10] Add structured-merge-diff vendor This is where most of the logic actually lives. --- .../sigs.k8s.io/structured-merge-diff/LICENSE | 201 +++++++++ .../structured-merge-diff/fieldpath/BUILD | 31 ++ .../structured-merge-diff/fieldpath/doc.go | 21 + .../fieldpath/element.go | 184 ++++++++ .../fieldpath/fromvalue.go | 123 ++++++ .../fieldpath/managers.go | 73 ++++ .../structured-merge-diff/fieldpath/path.go | 75 ++++ .../structured-merge-diff/fieldpath/set.go | 305 +++++++++++++ .../structured-merge-diff/merge/BUILD | 30 ++ .../structured-merge-diff/merge/conflict.go | 91 ++++ .../structured-merge-diff/merge/update.go | 142 ++++++ .../structured-merge-diff/schema/BUILD | 27 ++ .../structured-merge-diff/schema/doc.go | 28 ++ .../structured-merge-diff/schema/elements.go | 219 ++++++++++ .../schema/schemaschema.go | 128 ++++++ .../structured-merge-diff/typed/BUILD | 36 ++ .../structured-merge-diff/typed/doc.go | 18 + .../structured-merge-diff/typed/helpers.go | 246 +++++++++++ .../structured-merge-diff/typed/merge.go | 410 ++++++++++++++++++ .../structured-merge-diff/typed/parser.go | 114 +++++ .../structured-merge-diff/typed/typed.go | 213 +++++++++ .../structured-merge-diff/typed/validate.go | 208 +++++++++ .../structured-merge-diff/value/BUILD | 28 ++ .../structured-merge-diff/value/doc.go | 21 + .../value/unstructured.go | 234 ++++++++++ .../structured-merge-diff/value/value.go | 139 ++++++ 26 files changed, 3345 insertions(+) create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/LICENSE create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/fieldpath/BUILD create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/fieldpath/doc.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/fieldpath/element.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/fieldpath/fromvalue.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/fieldpath/managers.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/merge/BUILD create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/merge/conflict.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/merge/update.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/schema/doc.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/schema/schemaschema.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/doc.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/merge.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/value/BUILD create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/value/doc.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/value/value.go 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" + } +} From 0e1d50e70fdc9ed838d75a7a1abbe5fa607d22a1 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Wed, 16 Jan 2019 21:14:42 -0800 Subject: [PATCH 02/10] API Machinery, Kubectl and tests --- pkg/api/pod/util_test.go | 4 + pkg/api/testing/defaulting_test.go | 1 - pkg/api/testing/serialization_test.go | 4 +- pkg/api/v1/persistentvolume/util_test.go | 6 +- pkg/api/v1/pod/util_test.go | 4 + pkg/controller/garbagecollector/operations.go | 5 +- pkg/features/kube_features.go | 1 + pkg/kubectl/cmd/apply/apply.go | 90 +++++- pkg/kubectl/cmd/diff/BUILD | 3 + pkg/kubectl/cmd/diff/diff.go | 157 ++++++---- pkg/kubectl/cmd/util/helpers.go | 16 +- pkg/master/master.go | 10 +- pkg/master/master_openapi_test.go | 9 +- staging/src/k8s.io/api/Godeps/Godeps.json | 2 +- .../test/integration/basic_test.go | 6 +- .../test/integration/subresources_test.go | 25 +- .../test/integration/versioning_test.go | 11 +- .../pkg/api/apitesting/naming/naming.go | 18 ++ .../k8s.io/apimachinery/pkg/api/errors/BUILD | 1 + .../apimachinery/pkg/api/errors/errors.go | 24 ++ .../k8s.io/apimachinery/pkg/api/meta/meta.go | 4 +- .../apimachinery/pkg/apis/meta/v1/helpers.go | 15 + .../pkg/apis/meta/v1/helpers_test.go | 3 +- .../apimachinery/pkg/apis/meta/v1/meta.go | 13 + .../pkg/apis/meta/v1/options_test.go | 62 ++++ .../apimachinery/pkg/apis/meta/v1/register.go | 2 + .../apimachinery/pkg/apis/meta/v1/types.go | 52 ++++ .../apis/meta/v1/unstructured/unstructured.go | 25 ++ .../meta/v1/unstructured/unstructured_test.go | 3 +- .../pkg/apis/meta/v1/validation/BUILD | 1 + .../pkg/apis/meta/v1/validation/validation.go | 10 + .../k8s.io/apimachinery/pkg/runtime/types.go | 1 + .../src/k8s.io/apimachinery/pkg/test/BUILD | 2 + .../pkg/test/api_meta_help_test.go | 6 +- .../pkg/test/api_meta_meta_test.go | 6 +- .../k8s.io/apimachinery/pkg/types/patch.go | 1 + .../src/k8s.io/apiserver/pkg/endpoints/BUILD | 3 + .../apiserver/pkg/endpoints/handlers/BUILD | 5 +- .../pkg/endpoints/handlers/create.go | 14 + .../pkg/endpoints/handlers/fieldmanager/BUILD | 35 +++ .../handlers/fieldmanager/fieldmanager.go | 174 +++++++++++ .../handlers/fieldmanager/internal/BUILD | 68 +++++ .../handlers/fieldmanager/internal/fields.go | 95 ++++++ .../fieldmanager/internal/fields_test.go | 109 +++++++ .../fieldmanager/internal/gvkparser.go | 116 ++++++++ .../fieldmanager/internal/managedfields.go | 119 ++++++++ .../internal/managedfields_test.go | 143 +++++++++ .../fieldmanager/internal/pathelement.go | 140 +++++++++ .../fieldmanager/internal/pathelement_test.go | 84 ++++++ .../fieldmanager/internal/typeconverter.go | 99 +++++++ .../internal/typeconverter_test.go | 109 +++++++ .../fieldmanager/internal/versionconverter.go | 83 ++++++ .../internal/versionconverter_test.go | 107 +++++++ .../apiserver/pkg/endpoints/handlers/patch.go | 275 ++++++++++++++---- .../apiserver/pkg/endpoints/handlers/rest.go | 7 +- .../pkg/endpoints/handlers/rest_test.go | 23 +- .../pkg/endpoints/handlers/update.go | 10 +- .../apiserver/pkg/endpoints/handlers/watch.go | 0 .../apiserver/pkg/endpoints/installer.go | 29 +- .../pkg/endpoints/patchhandler_test.go | 176 +++++++++++ .../apiserver/pkg/features/kube_features.go | 7 + .../apiserver/pkg/registry/rest/create.go | 8 + .../apiserver/pkg/registry/rest/update.go | 8 + .../apiserver/pkg/server/genericapiserver.go | 141 +++++---- .../pkg/genericclioptions/resource/helper.go | 4 +- .../client-go/deprecated-dynamic/client.go | 2 +- .../k8s.io/client-go/dynamic/client_test.go | 2 +- .../k8s.io/client-go/dynamic/fake/simple.go | 2 +- .../client-go/dynamic/fake/simple_test.go | 2 +- .../src/k8s.io/client-go/dynamic/interface.go | 2 +- .../src/k8s.io/client-go/dynamic/simple.go | 2 +- .../cluster-bootstrap/Godeps/Godeps.json | 2 +- .../k8s.io/component-base/Godeps/Godeps.json | 2 +- .../csi-translation-lib/Godeps/Godeps.json | 2 +- .../Godeps/Godeps.json | 2 +- .../src/k8s.io/kube-proxy/Godeps/Godeps.json | 2 +- .../k8s.io/kube-scheduler/Godeps/Godeps.json | 2 +- staging/src/k8s.io/kubelet/Godeps/Godeps.json | 2 +- test/integration/apiserver/BUILD | 5 +- test/integration/apiserver/apiserver_test.go | 1 + test/integration/apiserver/apply/BUILD | 35 +++ .../integration/apiserver/apply/apply_test.go | 206 +++++++++++++ test/integration/apiserver/apply/main_test.go | 27 ++ test/integration/apiserver/print_test.go | 1 + test/integration/auth/BUILD | 1 + test/integration/auth/rbac_test.go | 49 +++- test/integration/dryrun/dryrun_test.go | 4 +- test/integration/framework/BUILD | 1 + test/integration/framework/master_utils.go | 36 ++- 89 files changed, 2908 insertions(+), 278 deletions(-) create mode 100644 staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/options_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go mode change 100755 => 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go create mode 100644 test/integration/apiserver/apply/BUILD create mode 100644 test/integration/apiserver/apply/apply_test.go create mode 100644 test/integration/apiserver/apply/main_test.go diff --git a/pkg/api/pod/util_test.go b/pkg/api/pod/util_test.go index 29ae2cfc4c1..c2cdd1b9d89 100644 --- a/pkg/api/pod/util_test.go +++ b/pkg/api/pod/util_test.go @@ -176,6 +176,10 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n case reflect.Ptr: resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) case reflect.Struct: + // Specifically skip ObjectMeta because it has recursive types + if name == "ObjectMeta" { + break + } for i := 0; i < tp.NumField(); i++ { field := tp.Field(i) resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Child(field.Name), field.Name, field.Type).List()...) diff --git a/pkg/api/testing/defaulting_test.go b/pkg/api/testing/defaulting_test.go index 50453577272..dac954097dc 100644 --- a/pkg/api/testing/defaulting_test.go +++ b/pkg/api/testing/defaulting_test.go @@ -49,7 +49,6 @@ func TestDefaulting(t *testing.T) { {Group: "", Version: "v1", Kind: "ConfigMap"}: {}, {Group: "", Version: "v1", Kind: "ConfigMapList"}: {}, {Group: "", Version: "v1", Kind: "Endpoints"}: {}, - {Group: "", Version: "v1", Kind: "EndpointsList"}: {}, {Group: "", Version: "v1", Kind: "Namespace"}: {}, {Group: "", Version: "v1", Kind: "NamespaceList"}: {}, {Group: "", Version: "v1", Kind: "Node"}: {}, diff --git a/pkg/api/testing/serialization_test.go b/pkg/api/testing/serialization_test.go index 600728439f0..00b7aa309e6 100644 --- a/pkg/api/testing/serialization_test.go +++ b/pkg/api/testing/serialization_test.go @@ -26,7 +26,6 @@ import ( "testing" jsoniter "github.com/json-iterator/go" - appsv1 "k8s.io/api/apps/v1" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" @@ -162,9 +161,10 @@ var nonRoundTrippableTypes = sets.NewString( "DeleteOptions", "CreateOptions", "UpdateOptions", + "PatchOptions", ) -var commonKinds = []string{"Status", "ListOptions", "DeleteOptions", "ExportOptions", "GetOptions", "CreateOptions", "UpdateOptions"} +var commonKinds = []string{"Status", "ListOptions", "DeleteOptions", "ExportOptions", "GetOptions", "CreateOptions", "UpdateOptions", "PatchOptions"} // TestCommonKindsRegistered verifies that all group/versions registered with // the testapi package have the common kinds. diff --git a/pkg/api/v1/persistentvolume/util_test.go b/pkg/api/v1/persistentvolume/util_test.go index 62e60cc6e9e..bf653821464 100644 --- a/pkg/api/v1/persistentvolume/util_test.go +++ b/pkg/api/v1/persistentvolume/util_test.go @@ -18,9 +18,8 @@ package persistentvolume import ( "reflect" - "testing" - "strings" + "testing" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" @@ -248,6 +247,9 @@ func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect. case reflect.Ptr: secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...) case reflect.Struct: + if name == "ObjectMeta" { + break + } for i := 0; i < tp.NumField(); i++ { field := tp.Field(i) secretPaths.Insert(collectSecretPaths(t, path.Child(field.Name), field.Name, field.Type).List()...) diff --git a/pkg/api/v1/pod/util_test.go b/pkg/api/v1/pod/util_test.go index 1f0c01daf11..b9f3544dc66 100644 --- a/pkg/api/v1/pod/util_test.go +++ b/pkg/api/v1/pod/util_test.go @@ -340,6 +340,10 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n case reflect.Ptr: resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) case reflect.Struct: + // Specifically skip ObjectMeta because it has recursive types + if name == "ObjectMeta" { + break + } for i := 0; i < tp.NumField(); i++ { field := tp.Field(i) resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Child(field.Name), field.Name, field.Type).List()...) diff --git a/pkg/controller/garbagecollector/operations.go b/pkg/controller/garbagecollector/operations.go index fefec3c5930..93dd771984c 100644 --- a/pkg/controller/garbagecollector/operations.go +++ b/pkg/controller/garbagecollector/operations.go @@ -19,8 +19,6 @@ package garbagecollector import ( "fmt" - "k8s.io/klog" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" + "k8s.io/klog" ) // cluster scoped resources don't have namespaces. Default to the item's namespace, but clear it for cluster scoped resources @@ -81,7 +80,7 @@ func (gc *GarbageCollector) patchObject(item objectReference, patch []byte, pt t if err != nil { return nil, err } - return gc.dynamicClient.Resource(resource).Namespace(resourceDefaultNamespace(namespaced, item.Namespace)).Patch(item.Name, pt, patch, metav1.UpdateOptions{}) + return gc.dynamicClient.Resource(resource).Namespace(resourceDefaultNamespace(namespaced, item.Namespace)).Patch(item.Name, pt, patch, metav1.PatchOptions{}) } // TODO: Using Patch when strategicmerge supports deleting an entry from a diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index a3b523a8d77..b0dda32280c 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -484,6 +484,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS genericfeatures.APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha}, genericfeatures.APIListChunking: {Default: true, PreRelease: utilfeature.Beta}, genericfeatures.DryRun: {Default: true, PreRelease: utilfeature.Beta}, + genericfeatures.ServerSideApply: {Default: false, PreRelease: utilfeature.Alpha}, // inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: diff --git a/pkg/kubectl/cmd/apply/apply.go b/pkg/kubectl/cmd/apply/apply.go index d6fce3354b7..11ed554822a 100644 --- a/pkg/kubectl/cmd/apply/apply.go +++ b/pkg/kubectl/cmd/apply/apply.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" "strings" "time" @@ -65,16 +66,18 @@ type ApplyOptions struct { DeleteFlags *delete.DeleteFlags DeleteOptions *delete.DeleteOptions - Selector string - DryRun bool - ServerDryRun bool - Prune bool - PruneResources []pruneResource - cmdBaseName string - All bool - Overwrite bool - OpenAPIPatch bool - PruneWhitelist []string + ServerSideApply bool + ForceConflicts bool + Selector string + DryRun bool + ServerDryRun bool + Prune bool + PruneResources []pruneResource + cmdBaseName string + All bool + Overwrite bool + OpenAPIPatch bool + PruneWhitelist []string Validator validation.Schema Builder *resource.Builder @@ -178,6 +181,7 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.") cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it. Warning: --dry-run cannot accurately output the result of merging the local manifest and the server-side data. Use --server-dry-run to get the merged result instead.") cmdutil.AddIncludeUninitializedFlag(cmd) + cmdutil.AddServerSideApplyFlags(cmd) // apply subcommands cmd.AddCommand(NewCmdApplyViewLastApplied(f, ioStreams)) @@ -188,8 +192,18 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions } func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd) + o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd) o.DryRun = cmdutil.GetDryRunFlag(cmd) + if o.ForceConflicts && !o.ServerSideApply { + return fmt.Errorf("--force-conflicts only works with --server-side") + } + + if o.DryRun && o.ServerSideApply { + return fmt.Errorf("--dry-run doesn't work with --server-side") + } + if o.DryRun && o.ServerDryRun { return fmt.Errorf("--dry-run and --server-dry-run can't be used together") } @@ -293,6 +307,16 @@ func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource return pruneResources, nil } +func isIncompatibleServerError(err error) bool { + // 415: Unsupported media type means we're talking to a server which doesn't + // support server-side apply. + if _, ok := err.(*errors.StatusError); !ok { + // Non-StatusError means the error isn't because the server is incompatible. + return false + } + return err.(*errors.StatusError).Status().Code == http.StatusUnsupportedMediaType +} + func (o *ApplyOptions) Run() error { var openapiSchema openapi.Resources if o.OpenAPIPatch { @@ -356,6 +380,50 @@ func (o *ApplyOptions) Run() error { klog.V(4).Infof("error recording current command: %v", err) } + if o.ServerSideApply { + // Send the full object to be applied on the server side. + data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object) + if err != nil { + return cmdutil.AddSourceToErr("serverside-apply", info.Source, err) + } + options := metav1.PatchOptions{ + Force: &o.ForceConflicts, + } + if o.ServerDryRun { + options.DryRun = []string{metav1.DryRunAll} + } + obj, err := resource.NewHelper(info.Client, info.Mapping).Patch( + info.Namespace, + info.Name, + types.ApplyPatchType, + data, + &options, + ) + if err == nil { + info.Refresh(obj, true) + metadata, err := meta.Accessor(info.Object) + if err != nil { + return err + } + visitedUids.Insert(string(metadata.GetUID())) + count++ + if len(output) > 0 && !shortOutput { + objs = append(objs, info.Object) + return nil + } + printer, err := o.ToPrinter("serverside-applied") + if err != nil { + return err + } + return printer.PrintObj(info.Object, o.Out) + } else if !isIncompatibleServerError(err) { + return err + } + // If we're talking to a server which does not implement server-side apply, + // continue with the client side apply after this block. + klog.Warningf("serverside-apply incompatible server: %v", err) + } + // Get the modified configuration of the object. Embed the result // as an annotation in the modified configuration, so that it will appear // in the patch sent to the server. @@ -840,7 +908,7 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, names } } - options := metav1.UpdateOptions{} + options := metav1.PatchOptions{} if p.ServerDryRun { options.DryRun = []string{metav1.DryRunAll} } diff --git a/pkg/kubectl/cmd/diff/BUILD b/pkg/kubectl/cmd/diff/BUILD index 93e0fb64ebd..d5c0923f040 100644 --- a/pkg/kubectl/cmd/diff/BUILD +++ b/pkg/kubectl/cmd/diff/BUILD @@ -18,8 +18,11 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library", + "//staging/src/k8s.io/client-go/discovery:go_default_library", + "//staging/src/k8s.io/client-go/dynamic:go_default_library", "//vendor/github.com/jonboulle/clockwork:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/k8s.io/klog:go_default_library", diff --git a/pkg/kubectl/cmd/diff/diff.go b/pkg/kubectl/cmd/diff/diff.go index 1956cf1bb0f..e64e8a0990d 100644 --- a/pkg/kubectl/cmd/diff/diff.go +++ b/pkg/kubectl/cmd/diff/diff.go @@ -30,8 +30,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions/resource" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" "k8s.io/klog" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/apply" @@ -67,21 +70,38 @@ const maxRetries = 4 type DiffOptions struct { FilenameOptions resource.FilenameOptions + + ServerSideApply bool + ForceConflicts bool + + OpenAPISchema openapi.Resources + DiscoveryClient discovery.DiscoveryInterface + DynamicClient dynamic.Interface + DryRunVerifier *apply.DryRunVerifier + CmdNamespace string + EnforceNamespace bool + Builder *resource.Builder + Diff *DiffProgram } -func checkDiffArgs(cmd *cobra.Command, args []string) error { +func validateArgs(cmd *cobra.Command, args []string) error { if len(args) != 0 { return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) } return nil } -func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { - var options DiffOptions - diff := DiffProgram{ - Exec: exec.New(), - IOStreams: streams, +func NewDiffOptions(ioStreams genericclioptions.IOStreams) *DiffOptions { + return &DiffOptions{ + Diff: &DiffProgram{ + Exec: exec.New(), + IOStreams: ioStreams, + }, } +} + +func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + options := NewDiffOptions(streams) cmd := &cobra.Command{ Use: "diff -f FILENAME", DisableFlagsInUseLine: true, @@ -89,13 +109,15 @@ func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C Long: diffLong, Example: diffExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(checkDiffArgs(cmd, args)) - cmdutil.CheckErr(RunDiff(f, &diff, &options)) + cmdutil.CheckErr(options.Complete(f, cmd)) + cmdutil.CheckErr(validateArgs(cmd, args)) + cmdutil.CheckErr(options.Run()) }, } usage := "contains the configuration to diff" cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) + cmdutil.AddServerSideApplyFlags(cmd) cmd.MarkFlagRequired("filename") return cmd @@ -229,11 +251,13 @@ type Object interface { // InfoObject is an implementation of the Object interface. It gets all // the information from the Info object. type InfoObject struct { - LocalObj runtime.Object - Info *resource.Info - Encoder runtime.Encoder - OpenAPI openapi.Resources - Force bool + LocalObj runtime.Object + Info *resource.Info + Encoder runtime.Encoder + OpenAPI openapi.Resources + Force bool + ServerSideApply bool + ForceConflicts bool } var _ Object = &InfoObject{} @@ -246,6 +270,24 @@ func (obj InfoObject) Live() runtime.Object { // Returns the "merged" object, as it would look like if applied or // created. func (obj InfoObject) Merged() (runtime.Object, error) { + if obj.ServerSideApply { + data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj.LocalObj) + if err != nil { + return nil, err + } + options := metav1.PatchOptions{ + Force: &obj.ForceConflicts, + DryRun: []string{metav1.DryRunAll}, + } + return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).Patch( + obj.Info.Namespace, + obj.Info.Name, + types.ApplyPatchType, + data, + &options, + ) + } + // Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it. if obj.Live() == nil { // Dry-run create if the object doesn't exist. @@ -350,30 +392,50 @@ func isConflict(err error) bool { return err != nil && errors.IsConflict(err) } +func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + var err error + + o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd) + o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd) + if o.ForceConflicts && !o.ServerSideApply { + return fmt.Errorf("--force-conflicts only works with --server-side") + } + + if !o.ServerSideApply { + o.OpenAPISchema, err = f.OpenAPISchema() + if err != nil { + return err + } + } + + o.DiscoveryClient, err = f.ToDiscoveryClient() + if err != nil { + return err + } + + o.DynamicClient, err = f.DynamicClient() + if err != nil { + return err + } + + o.DryRunVerifier = &apply.DryRunVerifier{ + Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)), + OpenAPIGetter: o.DiscoveryClient, + } + + o.CmdNamespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.Builder = f.NewBuilder() + return nil +} + // RunDiff uses the factory to parse file arguments, find the version to // diff, and find each Info object for each files, and runs against the // differ. -func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error { - schema, err := f.OpenAPISchema() - if err != nil { - return err - } - - discovery, err := f.ToDiscoveryClient() - if err != nil { - return err - } - - dynamic, err := f.DynamicClient() - if err != nil { - return err - } - - dryRunVerifier := &apply.DryRunVerifier{ - Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(dynamic)), - OpenAPIGetter: discovery, - } - +func (o *DiffOptions) Run() error { differ, err := NewDiffer("LIVE", "MERGED") if err != nil { return err @@ -382,15 +444,10 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error { printer := Printer{} - cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace() - if err != nil { - return err - } - - r := f.NewBuilder(). + r := o.Builder. Unstructured(). - NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, &options.FilenameOptions). + NamespaceParam(o.CmdNamespace).DefaultNamespace(). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). Flatten(). Do() if err := r.Err(); err != nil { @@ -402,7 +459,7 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error { return err } - if err := dryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil { + if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil { return err } @@ -424,11 +481,13 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error { ) } obj := InfoObject{ - LocalObj: local, - Info: info, - Encoder: scheme.DefaultJSONEncoder(), - OpenAPI: schema, - Force: force, + LocalObj: local, + Info: info, + Encoder: scheme.DefaultJSONEncoder(), + OpenAPI: o.OpenAPISchema, + Force: force, + ServerSideApply: o.ServerSideApply, + ForceConflicts: o.ForceConflicts, } err = differ.Diff(obj, printer) @@ -442,5 +501,5 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error { return err } - return differ.Run(diff) + return differ.Run(o.Diff) } diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index 750738cc42f..303b5fea97b 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -29,8 +29,6 @@ import ( "github.com/evanphx/json-patch" "github.com/spf13/cobra" "github.com/spf13/pflag" - "k8s.io/klog" - kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -44,6 +42,7 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/scale" "k8s.io/client-go/tools/clientcmd" + "k8s.io/klog" utilexec "k8s.io/utils/exec" ) @@ -400,6 +399,11 @@ func AddDryRunFlag(cmd *cobra.Command) { cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.") } +func AddServerSideApplyFlags(cmd *cobra.Command) { + cmd.Flags().Bool("server-side", false, "If true, apply runs in the server instead of the client.") + cmd.Flags().Bool("force-conflicts", false, "If true, server-side apply will force the changes against conflicts.") +} + func AddIncludeUninitializedFlag(cmd *cobra.Command) { cmd.Flags().Bool("include-uninitialized", false, `If true, the kubectl command applies to uninitialized objects. If explicitly set to false, this flag overrides other flags that make the kubectl commands apply to uninitialized objects, e.g., "--all". Objects with empty metadata.initializers are regarded as initialized.`) cmd.Flags().MarkDeprecated("include-uninitialized", "The Initializers feature has been removed. This flag is now a no-op, and will be removed in v1.15") @@ -473,6 +477,14 @@ func DumpReaderToFile(reader io.Reader, filename string) error { return nil } +func GetServerSideApplyFlag(cmd *cobra.Command) bool { + return GetFlagBool(cmd, "server-side") +} + +func GetForceConflictsFlag(cmd *cobra.Command) bool { + return GetFlagBool(cmd, "force-conflicts") +} + func GetDryRunFlag(cmd *cobra.Command) bool { return GetFlagBool(cmd, "dry-run") } diff --git a/pkg/master/master.go b/pkg/master/master.go index be544b44f03..df96b9541ca 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -399,7 +399,7 @@ type RESTStorageProvider interface { // InstallAPIs will install the APIs for the restStorageProviders if they are enabled. func (m *Master) InstallAPIs(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) { - apiGroupsInfo := []genericapiserver.APIGroupInfo{} + apiGroupsInfo := []*genericapiserver.APIGroupInfo{} for _, restStorageBuilder := range restStorageProviders { groupName := restStorageBuilder.GroupName() @@ -422,13 +422,11 @@ func (m *Master) InstallAPIs(apiResourceConfigSource serverstorage.APIResourceCo m.GenericAPIServer.AddPostStartHookOrDie(name, hook) } - apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo) + apiGroupsInfo = append(apiGroupsInfo, &apiGroupInfo) } - for i := range apiGroupsInfo { - if err := m.GenericAPIServer.InstallAPIGroup(&apiGroupsInfo[i]); err != nil { - klog.Fatalf("Error in registering group versions: %v", err) - } + if err := m.GenericAPIServer.InstallAPIGroups(apiGroupsInfo...); err != nil { + klog.Fatalf("Error in registering group versions: %v", err) } } diff --git a/pkg/master/master_openapi_test.go b/pkg/master/master_openapi_test.go index ca7cd48aa88..beed6be5f01 100644 --- a/pkg/master/master_openapi_test.go +++ b/pkg/master/master_openapi_test.go @@ -27,15 +27,14 @@ import ( "net/http/httptest" "testing" - openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/kubernetes/pkg/api/legacyscheme" - openapigen "k8s.io/kubernetes/pkg/generated/openapi" - "github.com/go-openapi/loads" "github.com/go-openapi/spec" "github.com/go-openapi/strfmt" "github.com/go-openapi/validate" + openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/kubernetes/pkg/api/legacyscheme" + openapigen "k8s.io/kubernetes/pkg/generated/openapi" ) // TestValidOpenAPISpec verifies that the open api is added diff --git a/staging/src/k8s.io/api/Godeps/Godeps.json b/staging/src/k8s.io/api/Godeps/Godeps.json index e5c05264b10..ba7a314593f 100644 --- a/staging/src/k8s.io/api/Godeps/Godeps.json +++ b/staging/src/k8s.io/api/Godeps/Godeps.json @@ -24,7 +24,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/json-iterator/go", diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go index b9f34b3cb4d..f9ed60c5f96 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go @@ -677,7 +677,7 @@ func TestPatch(t *testing.T) { t.Logf("Patching .num.num2 to 999") patch := []byte(`{"num": {"num2":999}}`) - patchedNoxuInstance, err := noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}) + patchedNoxuInstance, err := noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -692,7 +692,7 @@ func TestPatch(t *testing.T) { // a patch with no change t.Logf("Patching .num.num2 again to 999") - patchedNoxuInstance, err = noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}) + patchedNoxuInstance, err = noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -702,7 +702,7 @@ func TestPatch(t *testing.T) { // an empty patch t.Logf("Applying empty patch") - patchedNoxuInstance, err = noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`), metav1.UpdateOptions{}) + patchedNoxuInstance, err = noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`), metav1.PatchOptions{}) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go index c3a7c1cb214..d6c4552e7c3 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go @@ -25,6 +25,10 @@ import ( "testing" autoscaling "k8s.io/api/autoscaling/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" + "k8s.io/apiextensions-apiserver/test/integration/fixtures" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -33,11 +37,6 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" "k8s.io/client-go/dynamic" - - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" - "k8s.io/apiextensions-apiserver/test/integration/fixtures" ) var labelSelectorPath = ".status.labelSelector" @@ -774,7 +773,7 @@ func TestSubresourcePatch(t *testing.T) { t.Logf("Patching .status.num to 999") patch := []byte(`{"spec": {"num":999}, "status": {"num":999}}`) - patchedNoxuInstance, err := noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}, "status") + patchedNoxuInstance, err := noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}, "status") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -802,7 +801,7 @@ func TestSubresourcePatch(t *testing.T) { // no-op patch t.Logf("Patching .status.num again to 999") - patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}, "status") + patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}, "status") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -813,7 +812,7 @@ func TestSubresourcePatch(t *testing.T) { // empty patch t.Logf("Applying empty patch") - patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`), metav1.UpdateOptions{}, "status") + patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`), metav1.PatchOptions{}, "status") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -825,7 +824,7 @@ func TestSubresourcePatch(t *testing.T) { t.Logf("Patching .spec.replicas to 7") patch = []byte(`{"spec": {"replicas":7}, "status": {"replicas":7}}`) - patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}, "scale") + patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}, "scale") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -865,7 +864,7 @@ func TestSubresourcePatch(t *testing.T) { // no-op patch t.Logf("Patching .spec.replicas again to 7") - patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}, "scale") + patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}, "scale") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -876,7 +875,7 @@ func TestSubresourcePatch(t *testing.T) { // empty patch t.Logf("Applying empty patch") - patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`), metav1.UpdateOptions{}, "scale") + patchedNoxuInstance, err = noxuResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`), metav1.PatchOptions{}, "scale") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -886,12 +885,12 @@ func TestSubresourcePatch(t *testing.T) { expectString(t, patchedNoxuInstance.UnstructuredContent(), rv, "metadata", "resourceVersion") // make sure strategic merge patch is not supported for both status and scale - _, err = noxuResourceClient.Patch("foo", types.StrategicMergePatchType, patch, metav1.UpdateOptions{}, "status") + _, err = noxuResourceClient.Patch("foo", types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "status") if err == nil { t.Fatalf("unexpected non-error: strategic merge patch is not supported for custom resources") } - _, err = noxuResourceClient.Patch("foo", types.StrategicMergePatchType, patch, metav1.UpdateOptions{}, "scale") + _, err = noxuResourceClient.Patch("foo", types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "scale") if err == nil { t.Fatalf("unexpected non-error: strategic merge patch is not supported for custom resources") } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/versioning_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/versioning_test.go index c27945fe7af..4fdccff9dad 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/versioning_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/versioning_test.go @@ -24,13 +24,12 @@ import ( "time" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apiextensions-apiserver/test/integration/fixtures" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" ) func TestInternalVersionIsHandlerVersion(t *testing.T) { @@ -88,7 +87,7 @@ func TestInternalVersionIsHandlerVersion(t *testing.T) { patch := []byte(fmt.Sprintf(`{"i": %d}`, i)) i++ - _, err := noxuNamespacedResourceClientV1beta1.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}) + _, err := noxuNamespacedResourceClientV1beta1.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { // work around "grpc: the client connection is closing" error // TODO: fix the grpc error @@ -111,7 +110,7 @@ func TestInternalVersionIsHandlerVersion(t *testing.T) { patch := []byte(fmt.Sprintf(`{"i": %d}`, i)) i++ - _, err := noxuNamespacedResourceClientV1beta2.Patch("foo", types.MergePatchType, patch, metav1.UpdateOptions{}) + _, err := noxuNamespacedResourceClientV1beta2.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}) assert.NotNil(t, err) // work around "grpc: the client connection is closing" error diff --git a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/naming/naming.go b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/naming/naming.go index f65e86b6e1e..b000063be56 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/apitesting/naming/naming.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/apitesting/naming/naming.go @@ -69,6 +69,10 @@ func ensureNoTags(gvk schema.GroupVersionKind, tp reflect.Type, parents []reflec return errs } + // Don't look at the same type multiple times + if containsType(parents, tp) { + return nil + } parents = append(parents, tp) switch tp.Kind() { @@ -106,6 +110,10 @@ func ensureTags(gvk schema.GroupVersionKind, tp reflect.Type, parents []reflect. return errs } + // Don't look at the same type multiple times + if containsType(parents, tp) { + return nil + } parents = append(parents, tp) switch tp.Kind() { @@ -144,3 +152,13 @@ func fmtParentString(parents []reflect.Type) string { } return str } + +// containsType returns true if s contains t, false otherwise +func containsType(s []reflect.Type, t reflect.Type) bool { + for _, u := range s { + if t == u { + return true + } + } + return false +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD index 865a64fc583..027c99dd727 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD @@ -31,6 +31,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library", ], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go b/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go index e736a986140..81e5d48bf70 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/structured-merge-diff/merge" ) const ( @@ -184,6 +185,29 @@ func NewConflict(qualifiedResource schema.GroupResource, name string, err error) }} } +// NewApplyConflict returns an error including details on the requests apply conflicts +func NewApplyConflict(conflicts merge.Conflicts) *StatusError { + causes := make([]metav1.StatusCause, 0, len(conflicts)) + for _, conflict := range conflicts { + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseType("conflict"), + Message: conflict.Error(), + Field: conflict.Path.String(), + }) + } + + return &StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonConflict, + Details: &metav1.StatusDetails{ + // TODO: Get obj details here? + Causes: causes, + }, + Message: fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), conflicts.Error()), + }} +} + // NewGone returns an error indicating the item no longer available at the server and no forwarding address is known. func NewGone(message string) *StatusError { return &StatusError{metav1.Status{ diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go index 6fe7458f6c4..b50337e13f4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go @@ -20,14 +20,13 @@ import ( "fmt" "reflect" - "k8s.io/klog" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/klog" ) // errNotList is returned when an object implements the Object style interfaces but not the List style @@ -138,6 +137,7 @@ func AsPartialObjectMetadata(m metav1.Object) *metav1beta1.PartialObjectMetadata Finalizers: m.GetFinalizers(), ClusterName: m.GetClusterName(), Initializers: m.GetInitializers(), + ManagedFields: m.GetManagedFields(), }, } } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go index 604129ea101..b41d549a25b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go @@ -17,6 +17,7 @@ limitations under the License. package v1 import ( + "encoding/json" "fmt" "k8s.io/apimachinery/pkg/fields" @@ -243,4 +244,18 @@ func ResetObjectMetaForStatus(meta, existingMeta Object) { meta.SetAnnotations(existingMeta.GetAnnotations()) meta.SetFinalizers(existingMeta.GetFinalizers()) meta.SetOwnerReferences(existingMeta.GetOwnerReferences()) + meta.SetManagedFields(existingMeta.GetManagedFields()) } + +// MarshalJSON implements json.Marshaler +func (f Fields) MarshalJSON() ([]byte, error) { + return json.Marshal(&f.Map) +} + +// UnmarshalJSON implements json.Unmarshaler +func (f *Fields) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &f.Map) +} + +var _ json.Marshaler = Fields{} +var _ json.Unmarshaler = &Fields{} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go index 656e53af22c..649738c9815 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/google/gofuzz" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/diff" @@ -169,7 +168,7 @@ func TestResetObjectMetaForStatus(t *testing.T) { existingMeta := &ObjectMeta{} // fuzz the existingMeta to set every field, no nils - f := fuzz.New().NilChance(0).NumElements(1, 1) + f := fuzz.New().NilChance(0).NumElements(1, 1).MaxDepth(10) f.Fuzz(existingMeta) ResetObjectMetaForStatus(meta, existingMeta) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go index ee1447541fc..ea12b929cd8 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go @@ -63,6 +63,8 @@ type Object interface { SetOwnerReferences([]OwnerReference) GetClusterName() string SetClusterName(clusterName string) + GetManagedFields() map[string]VersionedFields + SetManagedFields(lastApplied map[string]VersionedFields) } // ListMetaAccessor retrieves the list interface from an object @@ -168,3 +170,14 @@ func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { } func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName } func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName } + +func (meta *ObjectMeta) GetManagedFields() map[string]VersionedFields { + return meta.ManagedFields +} + +func (meta *ObjectMeta) SetManagedFields(ManagedFields map[string]VersionedFields) { + meta.ManagedFields = make(map[string]VersionedFields, len(ManagedFields)) + for key, value := range ManagedFields { + meta.ManagedFields[key] = value + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/options_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/options_test.go new file mode 100644 index 00000000000..3367a57f07a --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/options_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2017 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 v1 + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + fuzz "github.com/google/gofuzz" +) + +func TestPatchOptionsIsSuperSetOfUpdateOptions(t *testing.T) { + f := fuzz.New() + for i := 0; i < 1000; i++ { + t.Run(fmt.Sprintf("Run %d/1000", i), func(t *testing.T) { + update := UpdateOptions{} + f.Fuzz(&update) + + b, err := json.Marshal(update) + if err != nil { + t.Fatalf("failed to marshal UpdateOptions (%v): %v", err, update) + } + patch := PatchOptions{} + err = json.Unmarshal(b, &patch) + if err != nil { + t.Fatalf("failed to unmarshal UpdateOptions into PatchOptions: %v", err) + } + + b, err = json.Marshal(patch) + if err != nil { + t.Fatalf("failed to marshal PatchOptions (%v): %v", err, patch) + } + got := UpdateOptions{} + err = json.Unmarshal(b, &got) + if err != nil { + t.Fatalf("failed to unmarshal UpdateOptions into UpdateOptions: %v", err) + } + + if !reflect.DeepEqual(update, got) { + t.Fatalf(`UpdateOptions -> PatchOptions -> UpdateOptions round-trip failed: +got: %v +want: %v`, got, update) + } + }) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go index 0827729d087..76d042a9661 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go @@ -55,6 +55,7 @@ func AddToGroupVersion(scheme *runtime.Scheme, groupVersion schema.GroupVersion) &DeleteOptions{}, &CreateOptions{}, &UpdateOptions{}, + &PatchOptions{}, ) utilruntime.Must(scheme.AddConversionFuncs( Convert_v1_WatchEvent_To_watch_Event, @@ -90,6 +91,7 @@ func init() { &DeleteOptions{}, &CreateOptions{}, &UpdateOptions{}, + &PatchOptions{}, ) // register manually. This usually goes through the SchemeBuilder, which we cannot use here. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go index f390bf02fc4..44f77732480 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go @@ -252,6 +252,16 @@ type ObjectMeta struct { // This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request. // +optional ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"` + + // ManagedFields is a map of workflow-id to the set of fields + // that are managed by that workflow. This is mostly for internal + // housekeeping, and users typically shouldn't need to set or + // understand this field. A workflow can be the user's name, a + // controller's name, or the name of a specific apply path like + // "ci-cd". The set of fields is always in the version that the + // workflow used when modifying the object. + // +optional + ManagedFields map[string]VersionedFields `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"` } // Initializers tracks the progress of initialization. @@ -494,7 +504,30 @@ type CreateOptions struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// PatchOptions may be provided when patching an API object. +// PatchOptions is meant to be a superset of UpdateOptions. +type PatchOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"` + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. Force + // flag must be unset for non-apply patch requests. + // +optional + Force *bool `json:"force,omitempty" protobuf:"varint,2,opt,name=force"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + // UpdateOptions may be provided when updating an API object. +// All fields in UpdateOptions should also be present in PatchOptions. type UpdateOptions struct { TypeMeta `json:",inline"` @@ -1009,3 +1042,22 @@ const ( LabelSelectorOpExists LabelSelectorOperator = "Exists" LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist" ) + +// VersionedFields is a pair of a FieldSet and the group version of the resource +// that the fieldset applies to. +type VersionedFields struct { + // APIVersion defines the version of this resource that this field set + // applies to. The format is "group/version" just like the top-level + // APIVersion field. It is necessary to track the version of a field + // set because it cannot be automatically converted. + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,1,opt,name=apiVersion"` + // Fields identifies a set of fields. + Fields Fields `json:"fields,omitempty" protobuf:"bytes,2,opt,name=fields,casttype=Fields"` +} + +// Fields stores a set of fields in a data structure like a Trie. +// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff +type Fields struct { + // Map stores a set of fields in a data structure like a Trie. + Map map[string]Fields `json:",inline" protobuf:"bytes,1,rep,name=map"` +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go index 781469ec265..f24d65928af 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go @@ -450,3 +450,28 @@ func (u *Unstructured) SetClusterName(clusterName string) { } u.setNestedField(clusterName, "metadata", "clusterName") } + +func (u *Unstructured) GetManagedFields() map[string]metav1.VersionedFields { + m, found, err := nestedMapNoCopy(u.Object, "metadata", "managedFields") + if !found || err != nil { + return nil + } + out := &map[string]metav1.VersionedFields{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, out); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object: %v", err)) + return nil + } + return *out +} + +func (u *Unstructured) SetManagedFields(managedFields map[string]metav1.VersionedFields) { + if managedFields == nil { + RemoveNestedField(u.Object, "metadata", "managedFields") + return + } + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&managedFields) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object: %v", err)) + } + u.setNestedField(out, "metadata", "managedFields") +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go index 4a03fff654e..c37699779a4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go @@ -22,7 +22,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/equality" metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" @@ -117,6 +116,7 @@ func TestUnstructuredMetadataOmitempty(t *testing.T) { u.SetInitializers(nil) u.SetFinalizers(nil) u.SetClusterName("") + u.SetManagedFields(nil) gotMetadata, _, err := unstructured.NestedFieldNoCopy(u.UnstructuredContent(), "metadata") if err != nil { @@ -159,4 +159,5 @@ func setObjectMetaUsingAccessors(u, uCopy *unstructured.Unstructured) { uCopy.SetInitializers(u.GetInitializers()) uCopy.SetFinalizers(u.GetFinalizers()) uCopy.SetClusterName(u.GetClusterName()) + uCopy.SetManagedFields(u.GetManagedFields()) } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD index ea9cc91b057..072713cbb4d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD @@ -20,6 +20,7 @@ go_library( importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/validation", deps = [ "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go index 81f86fb3068..02364d7fa31 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go @@ -18,6 +18,7 @@ package validation import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" @@ -97,6 +98,15 @@ func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList { return validateDryRun(field.NewPath("dryRun"), options.DryRun) } +func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList { + allErrs := field.ErrorList{} + if patchType != types.ApplyPatchType && options.Force != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch")) + } + allErrs = append(allErrs, validateDryRun(field.NewPath("dryRun"), options.DryRun)...) + return allErrs +} + var allowedDryRunValues = sets.NewString(metav1.DryRunAll) func validateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList { diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/types.go b/staging/src/k8s.io/apimachinery/pkg/runtime/types.go index e4515d8ed00..1f7f662e075 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/types.go @@ -42,6 +42,7 @@ type TypeMeta struct { const ( ContentTypeJSON string = "application/json" + ContentTypeYAML string = "application/yaml" ) // RawExtension is used to hold extensions in external versions. diff --git a/staging/src/k8s.io/apimachinery/pkg/test/BUILD b/staging/src/k8s.io/apimachinery/pkg/test/BUILD index d37f55ff3e1..baf857eb64e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/test/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/test/BUILD @@ -21,12 +21,14 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/api/apitesting:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/testapigroup:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/test/api_meta_help_test.go b/staging/src/k8s.io/apimachinery/pkg/test/api_meta_help_test.go index a68da324d67..b5e28cb0cab 100644 --- a/staging/src/k8s.io/apimachinery/pkg/test/api_meta_help_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/test/api_meta_help_test.go @@ -23,12 +23,14 @@ import ( fuzz "github.com/google/gofuzz" "k8s.io/apimachinery/pkg/api/meta" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/testapigroup" "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/diff" ) @@ -308,7 +310,9 @@ func TestSetListToMatchingType(t *testing.T) { } func TestSetExtractListRoundTrip(t *testing.T) { - fuzzer := fuzz.New().NilChance(0).NumElements(1, 5) + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + fuzzer := fuzz.New().NilChance(0).NumElements(1, 5).Funcs(metafuzzer.Funcs(codecs)...).MaxDepth(10) for i := 0; i < 5; i++ { start := &testapigroup.CarpList{} fuzzer.Fuzz(&start.Items) diff --git a/staging/src/k8s.io/apimachinery/pkg/test/api_meta_meta_test.go b/staging/src/k8s.io/apimachinery/pkg/test/api_meta_meta_test.go index 9da81d6d5c1..157360a191a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/test/api_meta_meta_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/test/api_meta_meta_test.go @@ -23,10 +23,12 @@ import ( "github.com/google/gofuzz" "k8s.io/apimachinery/pkg/api/meta" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/testapigroup" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" ) @@ -345,7 +347,9 @@ type MyAPIObject2 struct { } func getObjectMetaAndOwnerReferences() (myAPIObject2 MyAPIObject2, metaOwnerReferences []metav1.OwnerReference) { - fuzz.New().NilChance(.5).NumElements(1, 5).Fuzz(&myAPIObject2) + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + fuzz.New().NilChance(.5).NumElements(1, 5).Funcs(metafuzzer.Funcs(codecs)...).MaxDepth(10).Fuzz(&myAPIObject2) references := myAPIObject2.ObjectMeta.OwnerReferences // This is necessary for the test to pass because the getter will return a // non-nil slice. diff --git a/staging/src/k8s.io/apimachinery/pkg/types/patch.go b/staging/src/k8s.io/apimachinery/pkg/types/patch.go index d522d1dbdc6..fe8ecaaffa6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/types/patch.go +++ b/staging/src/k8s.io/apimachinery/pkg/types/patch.go @@ -25,4 +25,5 @@ const ( JSONPatchType PatchType = "application/json-patch+json" MergePatchType PatchType = "application/merge-patch+json" StrategicMergePatchType PatchType = "application/strategic-merge-patch+json" + ApplyPatchType PatchType = "application/apply-patch+yaml" ) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD index ad36f85ea72..1b0d7be70b1 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD @@ -82,10 +82,13 @@ go_library( "//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server/filters:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/github.com/emicklei/go-restful:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD index 009a8d4b2e0..b774f6c73ce 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD @@ -27,6 +27,7 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/apis/example:go_default_library", "//staging/src/k8s.io/apiserver/pkg/apis/example/v1:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", @@ -73,6 +74,7 @@ go_library( "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/audit:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library", @@ -86,8 +88,6 @@ go_library( "//vendor/github.com/evanphx/json-patch:go_default_library", "//vendor/golang.org/x/net/websocket:go_default_library", "//vendor/k8s.io/klog:go_default_library", - "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", - "//vendor/k8s.io/utils/trace:go_default_library", ], ) @@ -102,6 +102,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager:all-srcs", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:all-srcs", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:all-srcs", ], diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go index c28e44b9539..9a2f80e378e 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go @@ -133,6 +133,20 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte } } + if scope.FieldManager != nil { + liveObj, err := scope.Creater.New(scope.Kind) + if err != nil { + scope.err(fmt.Errorf("failed to create new object: %v", err), w, req) + return + } + + obj, err = scope.FieldManager.Update(liveObj, obj, "create") + if err != nil { + scope.err(fmt.Errorf("failed to update object managed fields: %v", err), w, req) + return + } + } + trace.Step("About to store object in database") result, err := finishRequest(timeout, func() (runtime.Object, error) { return r.Create( diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD new file mode 100644 index 00000000000..9ae6f2ce54d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fieldmanager.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager", + importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go new file mode 100644 index 00000000000..9e21d03acf1 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go @@ -0,0 +1,174 @@ +/* +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 fieldmanager + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" + openapiproto "k8s.io/kube-openapi/pkg/util/proto" + "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/merge" +) + +const applyManager = "apply" + +// FieldManager updates the managed fields and merge applied +// configurations. +type FieldManager struct { + typeConverter internal.TypeConverter + objectConverter runtime.ObjectConvertor + objectDefaulter runtime.ObjectDefaulter + groupVersion schema.GroupVersion + hubVersion schema.GroupVersion + updater merge.Updater +} + +// NewFieldManager creates a new FieldManager that merges apply requests +// and update managed fields for other types of requests. +func NewFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (*FieldManager, error) { + typeConverter, err := internal.NewTypeConverter(models) + if err != nil { + return nil, err + } + return &FieldManager{ + typeConverter: typeConverter, + objectConverter: objectConverter, + objectDefaulter: objectDefaulter, + groupVersion: gv, + hubVersion: hub, + updater: merge.Updater{ + Converter: internal.NewVersionConverter(typeConverter, objectConverter, hub), + }, + }, nil +} + +// Update is used when the object has already been merged (non-apply +// use-case), and simply updates the managed fields in the output +// object. +func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) { + managed, err := internal.DecodeObjectManagedFields(newObj) + // If the managed field is empty or we failed to decode it, + // let's try the live object + if err != nil || len(managed) == 0 { + managed, err = internal.DecodeObjectManagedFields(liveObj) + if err != nil { + return nil, fmt.Errorf("failed to decode managed fields: %v", err) + } + } + newObjVersioned, err := f.toVersioned(newObj) + if err != nil { + return nil, fmt.Errorf("failed to convert new object to proper version: %v", err) + } + liveObjVersioned, err := f.toVersioned(liveObj) + if err != nil { + return nil, fmt.Errorf("failed to convert live object to proper version: %v", err) + } + if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil { + return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err) + } + if err := internal.RemoveObjectManagedFields(newObjVersioned); err != nil { + return nil, fmt.Errorf("failed to remove managed fields from new obj: %v", err) + } + + newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned) + if err != nil { + return nil, fmt.Errorf("failed to create typed new object: %v", err) + } + liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned) + if err != nil { + return nil, fmt.Errorf("failed to create typed live object: %v", err) + } + apiVersion := fieldpath.APIVersion(f.groupVersion.String()) + managed, err = f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed, manager) + if err != nil { + return nil, fmt.Errorf("failed to update ManagedFields: %v", err) + } + + if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil { + return nil, fmt.Errorf("failed to encode managed fields: %v", err) + } + + return newObj, nil +} + +// Apply is used when server-side apply is called, as it merges the +// object and update the managed fields. +func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) (runtime.Object, error) { + managed, err := internal.DecodeObjectManagedFields(liveObj) + if err != nil { + return nil, fmt.Errorf("failed to decode managed fields: %v", err) + } + // We can assume that patchObj is already on the proper version: + // it shouldn't have to be converted so that it's not defaulted. + liveObjVersioned, err := f.toVersioned(liveObj) + if err != nil { + return nil, fmt.Errorf("failed to convert live object to proper version: %v", err) + } + if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil { + return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err) + } + + patchObjTyped, err := f.typeConverter.YAMLToTyped(patch) + if err != nil { + return nil, fmt.Errorf("failed to create typed patch object: %v", err) + } + liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned) + if err != nil { + return nil, fmt.Errorf("failed to create typed live object: %v", err) + } + apiVersion := fieldpath.APIVersion(f.groupVersion.String()) + newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, applyManager, force) + if err != nil { + if conflicts, ok := err.(merge.Conflicts); ok { + return nil, errors.NewApplyConflict(conflicts) + } + return nil, err + } + + newObj, err := f.typeConverter.TypedToObject(newObjTyped) + if err != nil { + return nil, fmt.Errorf("failed to convert new typed object to object: %v", err) + } + + if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil { + return nil, fmt.Errorf("failed to encode managed fields: %v", err) + } + + newObjVersioned, err := f.toVersioned(newObj) + if err != nil { + return nil, fmt.Errorf("failed to convert new object to proper version: %v", err) + } + f.objectDefaulter.Default(newObjVersioned) + + newObjUnversioned, err := f.toUnversioned(newObjVersioned) + if err != nil { + return nil, fmt.Errorf("failed to convert to unversioned: %v", err) + } + return newObjUnversioned, nil +} + +func (f *FieldManager) toVersioned(obj runtime.Object) (runtime.Object, error) { + return f.objectConverter.ConvertToVersion(obj, f.groupVersion) +} + +func (f *FieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) { + return f.objectConverter.ConvertToVersion(obj, f.hubVersion) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD new file mode 100644 index 00000000000..d8924d72890 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD @@ -0,0 +1,68 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "fields.go", + "gvkparser.go", + "managedfields.go", + "pathelement.go", + "typeconverter.go", + "versionconverter.go", + ], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal", + importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/schemaconv:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/typed:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library", + "//vendor/sigs.k8s.io/yaml:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "fields_test.go", + "managedfields_test.go", + "pathelement_test.go", + "typeconverter_test.go", + "versionconverter_test.go", + ], + data = ["//api/openapi-spec:swagger-spec"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/github.com/ghodss/yaml:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library", + "//vendor/sigs.k8s.io/yaml: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/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go new file mode 100644 index 00000000000..4fbf52c8b5b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go @@ -0,0 +1,95 @@ +/* +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 internal + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/structured-merge-diff/fieldpath" +) + +func newFields() metav1.Fields { + return metav1.Fields{Map: map[string]metav1.Fields{}} +} + +func fieldsSet(f metav1.Fields, path fieldpath.Path, set *fieldpath.Set) error { + if len(f.Map) == 0 { + set.Insert(path) + } + for k := range f.Map { + if k == "." { + set.Insert(path) + continue + } + pe, err := NewPathElement(k) + if err != nil { + return err + } + path = append(path, pe) + err = fieldsSet(f.Map[k], path, set) + if err != nil { + return err + } + path = path[:len(path)-1] + } + return nil +} + +// FieldsToSet creates a set paths from an input trie of fields +func FieldsToSet(f metav1.Fields) (fieldpath.Set, error) { + set := fieldpath.Set{} + return set, fieldsSet(f, fieldpath.Path{}, &set) +} + +func removeUselessDots(f metav1.Fields) metav1.Fields { + if _, ok := f.Map["."]; ok && len(f.Map) == 1 { + delete(f.Map, ".") + return f + } + for k, tf := range f.Map { + f.Map[k] = removeUselessDots(tf) + } + return f +} + +// SetToFields creates a trie of fields from an input set of paths +func SetToFields(s fieldpath.Set) (metav1.Fields, error) { + var err error + f := newFields() + s.Iterate(func(path fieldpath.Path) { + if err != nil { + return + } + tf := f + for _, pe := range path { + var str string + str, err = PathElementString(pe) + if err != nil { + break + } + if _, ok := tf.Map[str]; ok { + tf = tf.Map[str] + } else { + tf.Map[str] = newFields() + tf = tf.Map[str] + } + } + tf.Map["."] = newFields() + }) + f = removeUselessDots(f) + return f, err +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go new file mode 100644 index 00000000000..681c22cbcbb --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go @@ -0,0 +1,109 @@ +/* +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 internal + +import ( + "reflect" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/structured-merge-diff/fieldpath" +) + +// TestFieldsRoundTrip tests that a fields trie can be round tripped as a path set +func TestFieldsRoundTrip(t *testing.T) { + tests := []metav1.Fields{ + { + Map: map[string]metav1.Fields{ + "f:metadata": { + Map: map[string]metav1.Fields{ + ".": newFields(), + "f:name": newFields(), + }, + }, + }, + }, + } + + for _, test := range tests { + set, err := FieldsToSet(test) + if err != nil { + t.Fatalf("Failed to create path set: %v", err) + } + output, err := SetToFields(set) + if err != nil { + t.Fatalf("Failed to create fields trie from path set: %v", err) + } + if !reflect.DeepEqual(test, output) { + t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output) + } + } +} + +// TestFieldsToSetError tests that errors are picked up by FieldsToSet +func TestFieldsToSetError(t *testing.T) { + tests := []struct { + fields metav1.Fields + errString string + }{ + { + fields: metav1.Fields{ + Map: map[string]metav1.Fields{ + "k:{invalid json}": { + Map: map[string]metav1.Fields{ + ".": newFields(), + "f:name": newFields(), + }, + }, + }, + }, + errString: "invalid character", + }, + } + + for _, test := range tests { + _, err := FieldsToSet(test.fields) + if err == nil || !strings.Contains(err.Error(), test.errString) { + t.Fatalf("Expected error to contain %q but got: %v", test.errString, err) + } + } +} + +// TestSetToFieldsError tests that errors are picked up by SetToFields +func TestSetToFieldsError(t *testing.T) { + validName := "ok" + invalidPath := fieldpath.Path([]fieldpath.PathElement{{}, {FieldName: &validName}}) + + tests := []struct { + set fieldpath.Set + errString string + }{ + { + set: *fieldpath.NewSet(invalidPath), + errString: "Invalid type of path element", + }, + } + + for _, test := range tests { + _, err := SetToFields(test.set) + if err == nil || !strings.Contains(err.Error(), test.errString) { + t.Fatalf("Expected error to contain %q but got: %v", test.errString, err) + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go new file mode 100644 index 00000000000..ce9a0492445 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go @@ -0,0 +1,116 @@ +/* +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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/schemaconv" + "k8s.io/kube-openapi/pkg/util/proto" + "sigs.k8s.io/structured-merge-diff/typed" +) + +// groupVersionKindExtensionKey is the key used to lookup the +// GroupVersionKind value for an object definition from the +// definition's "extensions" map. +const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind" + +type gvkParser struct { + gvks map[schema.GroupVersionKind]string + parser typed.Parser +} + +func (p *gvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType { + typeName, ok := p.gvks[gvk] + if !ok { + return nil + } + return p.parser.Type(typeName) +} + +func newGVKParser(models proto.Models) (*gvkParser, error) { + typeSchema, err := schemaconv.ToSchema(models) + if err != nil { + return nil, fmt.Errorf("failed to convert models to schema: %v", err) + } + parser := gvkParser{ + gvks: map[schema.GroupVersionKind]string{}, + } + parser.parser = typed.Parser{Schema: *typeSchema} + for _, modelName := range models.ListModels() { + model := models.LookupModel(modelName) + if model == nil { + panic("ListModels returns a model that can't be looked-up.") + } + gvkList := parseGroupVersionKind(model) + for _, gvk := range gvkList { + if len(gvk.Kind) > 0 { + parser.gvks[gvk] = modelName + } + } + } + return &parser, nil +} + +// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. +func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind { + extensions := s.GetExtensions() + + gvkListResult := []schema.GroupVersionKind{} + + // Get the extensions + gvkExtension, ok := extensions[groupVersionKindExtensionKey] + if !ok { + return []schema.GroupVersionKind{} + } + + // gvk extension must be a list of at least 1 element. + gvkList, ok := gvkExtension.([]interface{}) + if !ok { + return []schema.GroupVersionKind{} + } + + for _, gvk := range gvkList { + // gvk extension list must be a map with group, version, and + // kind fields + gvkMap, ok := gvk.(map[interface{}]interface{}) + if !ok { + continue + } + group, ok := gvkMap["group"].(string) + if !ok { + continue + } + version, ok := gvkMap["version"].(string) + if !ok { + continue + } + kind, ok := gvkMap["kind"].(string) + if !ok { + continue + } + + gvkListResult = append(gvkListResult, schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: kind, + }) + } + + return gvkListResult +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go new file mode 100644 index 00000000000..ffe7485e986 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go @@ -0,0 +1,119 @@ +/* +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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/fieldpath" +) + +// RemoveObjectManagedFields removes the ManagedFields from the object +// before we merge so that it doesn't appear in the ManagedFields +// recursively. +func RemoveObjectManagedFields(obj runtime.Object) error { + accessor, err := meta.Accessor(obj) + if err != nil { + return fmt.Errorf("couldn't get accessor: %v", err) + } + accessor.SetManagedFields(nil) + return nil +} + +// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields. +func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, error) { + if from == nil { + return make(map[string]*fieldpath.VersionedSet), nil + } + accessor, err := meta.Accessor(from) + if err != nil { + return nil, fmt.Errorf("couldn't get accessor: %v", err) + } + + managed, err := decodeManagedFields(accessor.GetManagedFields()) + if err != nil { + return nil, fmt.Errorf("failed to convert managed fields from API: %v", err) + } + return managed, err +} + +// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields +func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedFields) error { + accessor, err := meta.Accessor(obj) + if err != nil { + return fmt.Errorf("couldn't get accessor: %v", err) + } + + managed, err := encodeManagedFields(fields) + if err != nil { + return fmt.Errorf("failed to convert back managed fields to API: %v", err) + } + accessor.SetManagedFields(managed) + + return nil +} + +// decodeManagedFields converts ManagedFields from the wire format (api format) +// to the format used by sigs.k8s.io/structured-merge-diff +func decodeManagedFields(encodedManagedFields map[string]metav1.VersionedFields) (managedFields fieldpath.ManagedFields, err error) { + managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields)) + for manager, encodedVersionedSet := range encodedManagedFields { + managedFields[manager], err = decodeVersionedSet(&encodedVersionedSet) + if err != nil { + return nil, fmt.Errorf("error decoding versioned set for %v: %v", manager, err) + } + } + return managedFields, nil +} + +func decodeVersionedSet(encodedVersionedSet *metav1.VersionedFields) (versionedSet *fieldpath.VersionedSet, err error) { + versionedSet = &fieldpath.VersionedSet{} + versionedSet.APIVersion = fieldpath.APIVersion(encodedVersionedSet.APIVersion) + set, err := FieldsToSet(encodedVersionedSet.Fields) + if err != nil { + return nil, fmt.Errorf("error decoding set: %v", err) + } + versionedSet.Set = &set + return versionedSet, nil +} + +// encodeManagedFields converts ManagedFields from the the format used by +// sigs.k8s.io/structured-merge-diff to the the wire format (api format) +func encodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields map[string]metav1.VersionedFields, err error) { + encodedManagedFields = make(map[string]metav1.VersionedFields, len(managedFields)) + for manager, versionedSet := range managedFields { + v, err := encodeVersionedSet(versionedSet) + if err != nil { + return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err) + } + encodedManagedFields[manager] = *v + } + return encodedManagedFields, nil +} + +func encodeVersionedSet(versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.VersionedFields, err error) { + encodedVersionedSet = &metav1.VersionedFields{} + encodedVersionedSet.APIVersion = string(versionedSet.APIVersion) + encodedVersionedSet.Fields, err = SetToFields(*versionedSet.Set) + if err != nil { + return nil, fmt.Errorf("error encoding set: %v", err) + } + return encodedVersionedSet, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go new file mode 100644 index 00000000000..dca8b93c73c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go @@ -0,0 +1,143 @@ +/* +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 internal + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/yaml" +) + +// TestRoundTripManagedFields will roundtrip ManagedFields from the format used by +// sigs.k8s.io/structured-merge-diff to the wire format (api format) and back +func TestRoundTripManagedFields(t *testing.T) { + tests := []string{ + `foo: + apiVersion: v1 + fields: + i:5: + f:i: {} + v:3: + f:alsoPi: {} + v:3.1415: + f:pi: {} + v:false: + f:notTrue: {} +`, + `foo: + apiVersion: v1 + fields: + f:spec: + f:containers: + k:{"name":"c"}: + f:image: {} + f:name: {} +`, + `foo: + apiVersion: v1 + fields: + f:apiVersion: {} + f:kind: {} + f:metadata: + f:labels: + f:app: {} + f:name: {} + f:spec: + f:replicas: {} + f:selector: + f:matchLabels: + f:app: {} + f:template: + f:medatada: + f:labels: + f:app: {} + f:spec: + f:containers: + k:{"name":"nginx"}: + .: {} + f:image: {} + f:name: {} + f:ports: + i:0: + f:containerPort: {} +`, + `foo: + apiVersion: v1 + fields: + f:allowVolumeExpansion: {} + f:apiVersion: {} + f:kind: {} + f:metadata: + f:name: {} + f:parameters: + f:resturl: {} + f:restuser: {} + f:secretName: {} + f:secretNamespace: {} + f:provisioner: {} +`, + `foo: + apiVersion: v1 + fields: + f:apiVersion: {} + f:kind: {} + f:metadata: + f:name: {} + f:spec: + f:group: {} + f:names: + f:kind: {} + f:plural: {} + f:shortNames: + i:0: {} + f:singular: {} + f:scope: {} + f:versions: + k:{"name":"v1"}: + f:name: {} + f:served: {} + f:storage: {} +`, + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + var unmarshaled map[string]metav1.VersionedFields + if err := yaml.Unmarshal([]byte(test), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + decoded, err := decodeManagedFields(unmarshaled) + if err != nil { + t.Fatalf("did not expect decoding error but got: %v", err) + } + encoded, err := encodeManagedFields(decoded) + if err != nil { + t.Fatalf("did not expect encoding error but got: %v", err) + } + marshaled, err := yaml.Marshal(&encoded) + if err != nil { + t.Fatalf("did not expect yaml marshalling error but got: %v", err) + } + if !reflect.DeepEqual(string(marshaled), test) { + t.Fatalf("expected:\n%v\nbut got:\n%v", test, string(marshaled)) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go new file mode 100644 index 00000000000..e2b63362f37 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go @@ -0,0 +1,140 @@ +/* +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 internal + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/value" +) + +const ( + // Field indicates that the content of this path element is a field's name + Field = "f" + + // Value indicates that the content of this path element is a field's value + Value = "v" + + // Index indicates that the content of this path element is an index in an array + Index = "i" + + // Key indicates that the content of this path element is a key value map + Key = "k" + + // Separator separates the type of a path element from the contents + Separator = ":" +) + +// NewPathElement parses a serialized path element +func NewPathElement(s string) (fieldpath.PathElement, error) { + split := strings.SplitN(s, Separator, 2) + if len(split) < 2 { + return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s) + } + switch split[0] { + case Field: + return fieldpath.PathElement{ + FieldName: &split[1], + }, nil + case Value: + val, err := value.FromJSON([]byte(split[1])) + if err != nil { + return fieldpath.PathElement{}, err + } + return fieldpath.PathElement{ + Value: &val, + }, nil + case Index: + i, err := strconv.Atoi(split[1]) + if err != nil { + return fieldpath.PathElement{}, err + } + return fieldpath.PathElement{ + Index: &i, + }, nil + case Key: + kv := map[string]json.RawMessage{} + err := json.Unmarshal([]byte(split[1]), &kv) + if err != nil { + return fieldpath.PathElement{}, err + } + fields := []value.Field{} + for k, v := range kv { + b, err := json.Marshal(v) + if err != nil { + return fieldpath.PathElement{}, err + } + val, err := value.FromJSON(b) + if err != nil { + return fieldpath.PathElement{}, err + } + + fields = append(fields, value.Field{ + Name: k, + Value: val, + }) + } + return fieldpath.PathElement{ + Key: fields, + }, nil + default: + // Ignore unknown key types + return fieldpath.PathElement{}, nil + } +} + +// PathElementString serializes a path element +func PathElementString(pe fieldpath.PathElement) (string, error) { + switch { + case pe.FieldName != nil: + return Field + Separator + *pe.FieldName, nil + case len(pe.Key) > 0: + kv := map[string]json.RawMessage{} + for _, k := range pe.Key { + b, err := k.Value.ToJSON() + if err != nil { + return "", err + } + m := json.RawMessage{} + err = json.Unmarshal(b, &m) + if err != nil { + return "", err + } + kv[k.Name] = m + } + b, err := json.Marshal(kv) + if err != nil { + return "", err + } + return Key + ":" + string(b), nil + case pe.Value != nil: + b, err := pe.Value.ToJSON() + if err != nil { + return "", err + } + return Value + ":" + string(b), nil + case pe.Index != nil: + return Index + ":" + strconv.Itoa(*pe.Index), nil + default: + return "", errors.New("Invalid type of path element") + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go new file mode 100644 index 00000000000..81b9dd41765 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go @@ -0,0 +1,84 @@ +/* +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 internal + +import "testing" + +func TestPathElementRoundTrip(t *testing.T) { + tests := []string{ + "i:0", + "i:1234", + "f:", + "f:spec", + "f:more-complicated-string", + "k:{\"name\":\"my-container\"}", + "k:{\"port\":\"8080\",\"protocol\":\"TCP\"}", + "k:{\"optionalField\":null}", + "k:{\"jsonField\":{\"A\":1,\"B\":null,\"C\":\"D\",\"E\":{\"F\":\"G\"}}}", + "k:{\"listField\":[\"1\",\"2\",\"3\"]}", + "v:null", + "v:\"some-string\"", + "v:1234", + "v:{\"some\":\"json\"}", + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + pe, err := NewPathElement(test) + if err != nil { + t.Fatalf("Failed to create path element: %v", err) + } + output, err := PathElementString(pe) + if err != nil { + t.Fatalf("Failed to create string from path element: %v", err) + } + if test != output { + t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output) + } + }) + } +} + +func TestPathElementIgnoreUnknown(t *testing.T) { + _, err := NewPathElement("r:Hello") + if err != nil { + t.Fatalf("Unknown qualifiers should be ignored") + } +} + +func TestNewPathElementError(t *testing.T) { + tests := []string{ + "", + "no-colon", + "i:index is not a number", + "i:1.23", + "i:", + "v:invalid json", + "v:", + "k:invalid json", + "k:{\"name\":invalid}", + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + _, err := NewPathElement(test) + if err == nil { + t.Fatalf("Expected error, no error found") + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go new file mode 100644 index 00000000000..79f3017cb43 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go @@ -0,0 +1,99 @@ +/* +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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/util/proto" + "sigs.k8s.io/structured-merge-diff/typed" + "sigs.k8s.io/yaml" +) + +// TypeConverter allows you to convert from runtime.Object to +// typed.TypedValue and the other way around. +type TypeConverter interface { + NewTyped(schema.GroupVersionKind) (typed.TypedValue, error) + ObjectToTyped(runtime.Object) (typed.TypedValue, error) + YAMLToTyped([]byte) (typed.TypedValue, error) + TypedToObject(typed.TypedValue) (runtime.Object, error) +} + +type typeConverter struct { + parser *gvkParser +} + +var _ TypeConverter = &typeConverter{} + +// NewTypeConverter builds a TypeConverter from a proto.Models. This +// will automatically find the proper version of the object, and the +// corresponding schema information. +func NewTypeConverter(models proto.Models) (TypeConverter, error) { + parser, err := newGVKParser(models) + if err != nil { + return nil, err + } + return &typeConverter{parser: parser}, nil +} + +func (c *typeConverter) NewTyped(gvk schema.GroupVersionKind) (typed.TypedValue, error) { + t := c.parser.Type(gvk) + if t == nil { + return typed.TypedValue{}, fmt.Errorf("no corresponding type for %v", gvk) + } + + u, err := t.New() + if err != nil { + return typed.TypedValue{}, fmt.Errorf("new typed: %v", err) + } + return u, nil +} + +func (c *typeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) { + u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return typed.TypedValue{}, err + } + gvk := obj.GetObjectKind().GroupVersionKind() + t := c.parser.Type(gvk) + if t == nil { + return typed.TypedValue{}, fmt.Errorf("no corresponding type for %v", gvk) + } + return t.FromUnstructured(u) +} + +func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) { + unstructured := &unstructured.Unstructured{Object: map[string]interface{}{}} + + if err := yaml.Unmarshal(from, &unstructured.Object); err != nil { + return typed.TypedValue{}, fmt.Errorf("error decoding YAML: %v", err) + } + + return c.ObjectToTyped(unstructured) +} + +func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) { + vu := value.AsValue().ToUnstructured(false) + u, ok := vu.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("failed to convert typed to unstructured: want map, got %T", vu) + } + return &unstructured.Unstructured{Object: u}, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go new file mode 100644 index 00000000000..bced6d6328f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go @@ -0,0 +1,109 @@ +/* +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 internal_test + +import ( + "path/filepath" + "reflect" + "testing" + + "github.com/ghodss/yaml" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" + "k8s.io/kube-openapi/pkg/util/proto" + prototesting "k8s.io/kube-openapi/pkg/util/proto/testing" +) + +var fakeSchema = prototesting.Fake{ + Path: filepath.Join( + "..", "..", "..", "..", "..", "..", "..", "..", "..", + "api", "openapi-spec", "swagger.json"), +} + +func TestTypeConverter(t *testing.T) { + d, err := fakeSchema.OpenAPISchema() + if err != nil { + t.Fatalf("Failed to parse OpenAPI schema: %v", err) + } + m, err := proto.NewOpenAPIData(d) + if err != nil { + t.Fatalf("Failed to build OpenAPI models: %v", err) + } + + tc, err := internal.NewTypeConverter(m) + if err != nil { + t.Fatalf("Failed to build TypeConverter: %v", err) + } + + y := ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 +` + + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { + t.Fatalf("Failed to parse yaml object: %v", err) + } + typed, err := tc.ObjectToTyped(obj) + if err != nil { + t.Fatalf("Failed to convert object to typed: %v", err) + } + newObj, err := tc.TypedToObject(typed) + if err != nil { + t.Fatalf("Failed to convert typed to object: %v", err) + } + if !reflect.DeepEqual(obj, newObj) { + t.Errorf(`Round-trip failed: +Original object: +%#v +Final object: +%#v`, obj, newObj) + } + + yamlTyped, err := tc.YAMLToTyped([]byte(y)) + if err != nil { + t.Fatalf("Failed to convert yaml to typed: %v", err) + } + newObj, err = tc.TypedToObject(yamlTyped) + if err != nil { + t.Fatalf("Failed to convert typed to object: %v", err) + } + if !reflect.DeepEqual(obj, newObj) { + t.Errorf(`YAML conversion resulted in different object failed: +Original object: +%#v +Final object: +%#v`, obj, newObj) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go new file mode 100644 index 00000000000..05770b0aeb0 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go @@ -0,0 +1,83 @@ +/* +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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/merge" + "sigs.k8s.io/structured-merge-diff/typed" +) + +// versionConverter is an implementation of +// sigs.k8s.io/structured-merge-diff/merge.Converter +type versionConverter struct { + typeConverter TypeConverter + objectConvertor runtime.ObjectConvertor + hubVersion schema.GroupVersion +} + +var _ merge.Converter = &versionConverter{} + +// NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor. +func NewVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter { + return &versionConverter{ + typeConverter: t, + objectConvertor: o, + hubVersion: h, + } +} + +// Convert implements sigs.k8s.io/structured-merge-diff/merge.Converter +func (v *versionConverter) Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) { + // Convert the smd typed value to a kubernetes object. + objectToConvert, err := v.typeConverter.TypedToObject(object) + if err != nil { + return object, err + } + + // Parse the target groupVersion. + groupVersion, err := schema.ParseGroupVersion(string(version)) + if err != nil { + return object, err + } + + // If attempting to convert to the same version as we already have, just return it. + if objectToConvert.GetObjectKind().GroupVersionKind().GroupVersion() == groupVersion { + return object, nil + } + + // Convert to internal + internalObject, err := v.objectConvertor.ConvertToVersion(objectToConvert, v.hubVersion) + if err != nil { + return object, fmt.Errorf("failed to convert object (%v to %v): %v", + objectToConvert.GetObjectKind().GroupVersionKind(), v.hubVersion, err) + } + + // Convert the object into the target version + convertedObject, err := v.objectConvertor.ConvertToVersion(internalObject, groupVersion) + if err != nil { + return object, fmt.Errorf("failed to convert object (%v to %v): %v", + internalObject.GetObjectKind().GroupVersionKind(), groupVersion, err) + } + + // Convert the object back to a smd typed value and return it. + return v.typeConverter.ObjectToTyped(convertedObject) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go new file mode 100644 index 00000000000..18365f6d3a1 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go @@ -0,0 +1,107 @@ +/* +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 internal_test + +import ( + "fmt" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" + "k8s.io/kube-openapi/pkg/util/proto" + "sigs.k8s.io/structured-merge-diff/fieldpath" +) + +// TestVersionConverter tests the version converter +func TestVersionConverter(t *testing.T) { + d, err := fakeSchema.OpenAPISchema() + if err != nil { + t.Fatalf("Failed to parse OpenAPI schema: %v", err) + } + m, err := proto.NewOpenAPIData(d) + if err != nil { + t.Fatalf("Failed to build OpenAPI models: %v", err) + } + tc, err := internal.NewTypeConverter(m) + if err != nil { + t.Fatalf("Failed to build TypeConverter: %v", err) + } + oc := fakeObjectConvertor{ + gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"), + gvkForVersion("v1"): objForGroupVersion("apps/v1"), + } + vc := internal.NewVersionConverter(tc, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) + + input, err := tc.ObjectToTyped(objForGroupVersion("apps/v1beta1")) + if err != nil { + t.Fatalf("error creating converting input object to a typed value: %v", err) + } + expected := objForGroupVersion("apps/v1") + output, err := vc.Convert(input, fieldpath.APIVersion("apps/v1")) + if err != nil { + t.Fatalf("expected err to be nil but got %v", err) + } + actual, err := tc.TypedToObject(output) + if err != nil { + t.Fatalf("error converting output typed value to an object %v", err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected to get %v but got %v", expected, actual) + } +} + +func gvkForVersion(v string) schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: "apps", + Version: v, + Kind: "Deployment", + } +} + +func objForGroupVersion(gv string) runtime.Object { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": gv, + "kind": "Deployment", + }, + } +} + +type fakeObjectConvertor map[schema.GroupVersionKind]runtime.Object + +var _ runtime.ObjectConvertor = fakeObjectConvertor{} + +func (c fakeObjectConvertor) ConvertToVersion(_ runtime.Object, gv runtime.GroupVersioner) (runtime.Object, error) { + allKinds := make([]schema.GroupVersionKind, 0) + for kind := range c { + allKinds = append(allKinds, kind) + } + gvk, _ := gv.KindForGroupVersionKinds(allKinds) + return c[gvk], nil +} + +func (fakeObjectConvertor) Convert(_, _, _ interface{}) error { + return fmt.Errorf("function not implemented") +} + +func (fakeObjectConvertor) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) { + return "", "", fmt.Errorf("function not implemented") +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go index 384c276685b..b674dcdd4b1 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go @@ -24,8 +24,8 @@ import ( "time" "github.com/evanphx/json-patch" - "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/validation" @@ -38,6 +38,8 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/audit" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/features" @@ -94,20 +96,20 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface return } - patchJS, err := readBody(req) + patchBytes, err := readBody(req) if err != nil { scope.err(err, w, req) return } - options := &metav1.UpdateOptions{} + options := &metav1.PatchOptions{} if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil { err = errors.NewBadRequest(err.Error()) scope.err(err, w, req) return } - if errs := validation.ValidateUpdateOptions(options); len(errs) > 0 { - err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "UpdateOptions"}, "", errs) + if errs := validation.ValidatePatchOptions(options, patchType); len(errs) > 0 { + err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "PatchOptions"}, "", errs) scope.err(err, w, req) return } @@ -115,12 +117,16 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface ae := request.AuditEventFrom(ctx) admit = admission.WithAudit(admit, ae) - audit.LogRequestPatch(ae, patchJS) + audit.LogRequestPatch(ae, patchBytes) trace.Step("Recorded the audit event") - s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON) + baseContentType := runtime.ContentTypeJSON + if patchType == types.ApplyPatchType { + baseContentType = runtime.ContentTypeYAML + } + s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType) if !ok { - scope.err(fmt.Errorf("no serializer defined for JSON"), w, req) + scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req) return } gv := scope.Kind.GroupVersion() @@ -131,7 +137,18 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface ) userInfo, _ := request.UserFrom(ctx) - staticAdmissionAttributes := admission.NewAttributesRecord( + staticCreateAttributes := admission.NewAttributesRecord( + nil, + nil, + scope.Kind, + namespace, + name, + scope.Resource, + scope.Subresource, + admission.Create, + dryrun.IsDryRun(options.DryRun), + userInfo) + staticUpdateAttributes := admission.NewAttributesRecord( nil, nil, scope.Kind, @@ -143,38 +160,37 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface dryrun.IsDryRun(options.DryRun), userInfo, ) - admissionCheck := func(updatedObject runtime.Object, currentObject runtime.Object) error { - // if we allow create-on-patch, we have this TODO: call the mutating admission chain with the CREATE verb instead of UPDATE - if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && admit.Handles(admission.Update) { - return mutatingAdmission.Admit(admission.NewAttributesRecord( - updatedObject, - currentObject, - scope.Kind, - namespace, - name, - scope.Resource, - scope.Subresource, - admission.Update, - dryrun.IsDryRun(options.DryRun), - userInfo, - )) - } - return nil + + mutatingAdmission, _ := admit.(admission.MutationInterface) + createAuthorizerAttributes := authorizer.AttributesRecord{ + User: userInfo, + ResourceRequest: true, + Path: req.URL.Path, + Verb: "create", + APIGroup: scope.Resource.Group, + APIVersion: scope.Resource.Version, + Resource: scope.Resource.Resource, + Subresource: scope.Subresource, + Namespace: namespace, + Name: name, } p := patcher{ namer: scope.Namer, creater: scope.Creater, defaulter: scope.Defaulter, + typer: scope.Typer, unsafeConvertor: scope.UnsafeConvertor, kind: scope.Kind, resource: scope.Resource, + subresource: scope.Subresource, + dryRun: dryrun.IsDryRun(options.DryRun), hubGroupVersion: scope.HubGroupVersion, - createValidation: rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes), - updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes), - admissionCheck: admissionCheck, + createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes), scope.Authorizer, createAuthorizerAttributes), + updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes), + admissionCheck: mutatingAdmission, codec: codec, @@ -184,20 +200,35 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface restPatcher: r, name: name, patchType: patchType, - patchJS: patchJS, + patchBytes: patchBytes, trace: trace, } - result, err := p.patchResource(ctx) + result, wasCreated, err := p.patchResource(ctx, scope) if err != nil { scope.err(err, w, req) return } trace.Step("Object stored in database") + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + scope.err(fmt.Errorf("missing requestInfo"), w, req) + return + } + if err := setSelfLink(result, requestInfo, scope.Namer); err != nil { + scope.err(err, w, req) + return + } + trace.Step("Self-link added") + + status := http.StatusOK + if wasCreated { + status = http.StatusCreated + } scope.Trace = trace - transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result) + transformResponseObject(ctx, scope, req, w, status, outputMediaType, result) } } @@ -213,27 +244,30 @@ type patcher struct { namer ScopeNamer creater runtime.ObjectCreater defaulter runtime.ObjectDefaulter + typer runtime.ObjectTyper unsafeConvertor runtime.ObjectConvertor resource schema.GroupVersionResource kind schema.GroupVersionKind + subresource string + dryRun bool hubGroupVersion schema.GroupVersion // Validation functions createValidation rest.ValidateObjectFunc updateValidation rest.ValidateObjectUpdateFunc - admissionCheck mutateObjectUpdateFunc + admissionCheck admission.MutationInterface codec runtime.Codec timeout time.Duration - options *metav1.UpdateOptions + options *metav1.PatchOptions // Operation information restPatcher rest.Patcher name string patchType types.PatchType - patchJS []byte + patchBytes []byte trace *utiltrace.Trace @@ -241,14 +275,18 @@ type patcher struct { namespace string updatedObjectInfo rest.UpdatedObjectInfo mechanism patchMechanism + forceAllowCreate bool } type patchMechanism interface { applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) + createNewObject() (runtime.Object, error) } type jsonPatcher struct { *patcher + + fieldManager *fieldmanager.FieldManager } func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) { @@ -270,15 +308,24 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r return nil, err } + if p.fieldManager != nil { + if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, "jsonPatcher"); err != nil { + return nil, fmt.Errorf("failed to update object managed fields: %v", err) + } + } return objToUpdate, nil } -// patchJS applies the patch. Input and output objects must both have +func (p *jsonPatcher) createNewObject() (runtime.Object, error) { + return nil, errors.NewNotFound(p.resource.GroupResource(), p.name) +} + +// applyJSPatch applies the patch. Input and output objects must both have // the external version, since that is what the patch must have been constructed against. func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) { switch p.patchType { case types.JSONPatchType: - patchObj, err := jsonpatch.DecodePatch(p.patchJS) + patchObj, err := jsonpatch.DecodePatch(p.patchBytes) if err != nil { return nil, errors.NewBadRequest(err.Error()) } @@ -288,7 +335,7 @@ func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr } return patchedJS, nil case types.MergePatchType: - return jsonpatch.MergePatch(versionedJS, p.patchJS) + return jsonpatch.MergePatch(versionedJS, p.patchBytes) default: // only here as a safety net - go-restful filters content-type return nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType) @@ -300,6 +347,7 @@ type smpPatcher struct { // Schema schemaReferenceObj runtime.Object + fieldManager *fieldmanager.FieldManager } func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) { @@ -313,22 +361,60 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru if err != nil { return nil, err } - if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchJS, versionedObjToUpdate, p.schemaReferenceObj); err != nil { + if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj); err != nil { return nil, err } // Convert the object back to the hub version - return p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion) + newObj, err := p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion) + if err != nil { + return nil, err + } + + if p.fieldManager != nil { + if newObj, err = p.fieldManager.Update(currentObject, newObj, "smPatcher"); err != nil { + return nil, fmt.Errorf("failed to update object managed fields: %v", err) + } + } + return newObj, nil } -// strategicPatchObject applies a strategic merge patch of to +func (p *smpPatcher) createNewObject() (runtime.Object, error) { + return nil, errors.NewNotFound(p.resource.GroupResource(), p.name) +} + +type applyPatcher struct { + patch []byte + options *metav1.PatchOptions + creater runtime.ObjectCreater + kind schema.GroupVersionKind + fieldManager *fieldmanager.FieldManager +} + +func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) { + force := false + if p.options.Force != nil { + force = *p.options.Force + } + return p.fieldManager.Apply(obj, p.patch, force) +} + +func (p *applyPatcher) createNewObject() (runtime.Object, error) { + obj, err := p.creater.New(p.kind) + if err != nil { + return nil, fmt.Errorf("failed to create new object: %v", obj) + } + return p.applyPatchToCurrentObject(obj) +} + +// strategicPatchObject applies a strategic merge patch of to // and stores the result in . // It additionally returns the map[string]interface{} representation of the -// and . +// and . // NOTE: Both and are supposed to be versioned. func strategicPatchObject( defaulter runtime.ObjectDefaulter, originalObject runtime.Object, - patchJS []byte, + patchBytes []byte, objToUpdate runtime.Object, schemaReferenceObj runtime.Object, ) error { @@ -338,7 +424,7 @@ func strategicPatchObject( } patchMap := make(map[string]interface{}) - if err := json.Unmarshal(patchJS, &patchMap); err != nil { + if err := json.Unmarshal(patchBytes, &patchMap); err != nil { return errors.NewBadRequest(err.Error()) } @@ -350,52 +436,113 @@ func strategicPatchObject( // applyPatch is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object as input. -func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (runtime.Object, error) { +// TODO: rename this function because the name implies it is related to applyPatcher +func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { // Make sure we actually have a persisted currentObject p.trace.Step("About to apply patch") - if hasUID, err := hasUID(currentObject); err != nil { + currentObjectHasUID, err := hasUID(currentObject) + if err != nil { return nil, err - } else if !hasUID { - return nil, errors.NewNotFound(p.resource.GroupResource(), p.name) + } else if !currentObjectHasUID { + objToUpdate, patchErr = p.mechanism.createNewObject() + } else { + objToUpdate, patchErr = p.mechanism.applyPatchToCurrentObject(currentObject) } - objToUpdate, err := p.mechanism.applyPatchToCurrentObject(currentObject) + if patchErr != nil { + return nil, patchErr + } + + objToUpdateHasUID, err := hasUID(objToUpdate) if err != nil { return nil, err } + if objToUpdateHasUID && !currentObjectHasUID { + accessor, err := meta.Accessor(objToUpdate) + if err != nil { + return nil, err + } + return nil, errors.NewConflict(p.resource.GroupResource(), p.name, fmt.Errorf("uid mismatch: the provided object specified uid %s, and no existing object was found", accessor.GetUID())) + } + if err := checkName(objToUpdate, p.name, p.namespace, p.namer); err != nil { return nil, err } return objToUpdate, nil } +func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation) admission.Attributes { + userInfo, _ := request.UserFrom(ctx) + return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, p.dryRun, userInfo) +} + // applyAdmission is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object and the patched object as input. +// TODO: rename this function because the name implies it is related to applyPatcher func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { p.trace.Step("About to check admission control") - return patchedObject, p.admissionCheck(patchedObject, currentObject) + var operation admission.Operation + if hasUID, err := hasUID(currentObject); err != nil { + return nil, err + } else if !hasUID { + operation = admission.Create + currentObject = nil + } else { + operation = admission.Update + } + if p.admissionCheck != nil && p.admissionCheck.Handles(operation) { + attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation) + return patchedObject, p.admissionCheck.Admit(attributes) + } + return patchedObject, nil } // patchResource divides PatchResource for easier unit testing -func (p *patcher) patchResource(ctx context.Context) (runtime.Object, error) { +func (p *patcher) patchResource(ctx context.Context, scope RequestScope) (runtime.Object, bool, error) { p.namespace = request.NamespaceValue(ctx) switch p.patchType { case types.JSONPatchType, types.MergePatchType: - p.mechanism = &jsonPatcher{patcher: p} + p.mechanism = &jsonPatcher{ + patcher: p, + fieldManager: scope.FieldManager, + } case types.StrategicMergePatchType: schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion()) + if err != nil { + return nil, false, err + } + p.mechanism = &smpPatcher{ + patcher: p, + schemaReferenceObj: schemaReferenceObj, + fieldManager: scope.FieldManager, + } + // this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type + case types.ApplyPatchType: + p.mechanism = &applyPatcher{ + fieldManager: scope.FieldManager, + patch: p.patchBytes, + options: p.options, + creater: p.creater, + kind: p.kind, + } + p.forceAllowCreate = true + default: + return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType) + } + + wasCreated := false + p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission) + result, err := finishRequest(p.timeout, func() (runtime.Object, error) { + // TODO: Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate + options, err := patchToUpdateOptions(p.options) if err != nil { return nil, err } - p.mechanism = &smpPatcher{patcher: p, schemaReferenceObj: schemaReferenceObj} - default: - return nil, fmt.Errorf("%v: unimplemented patch type", p.patchType) - } - p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission) - return finishRequest(p.timeout, func() (runtime.Object, error) { - updateObject, _, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, false, p.options) + updateObject, created, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, p.forceAllowCreate, options) + wasCreated = created return updateObject, updateErr }) + return result, wasCreated, err } // applyPatchToObject applies a strategic merge patch of to @@ -434,3 +581,13 @@ func interpretStrategicMergePatchError(err error) error { return err } } + +func patchToUpdateOptions(po *metav1.PatchOptions) (*metav1.UpdateOptions, error) { + b, err := json.Marshal(po) + if err != nil { + return nil, err + } + uo := metav1.UpdateOptions{} + err = json.Unmarshal(b, &uo) + return &uo, err +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go index 43b0919452f..744ab6ea1ba 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go @@ -27,8 +27,6 @@ import ( "strings" "time" - "k8s.io/klog" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,11 +35,12 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/metrics" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - openapiproto "k8s.io/kube-openapi/pkg/util/proto" + "k8s.io/klog" utiltrace "k8s.io/utils/trace" ) @@ -61,7 +60,7 @@ type RequestScope struct { Trace *utiltrace.Trace TableConvertor rest.TableConvertor - OpenAPIModels openapiproto.Models + FieldManager *fieldmanager.FieldManager Resource schema.GroupVersionResource Kind schema.GroupVersionKind diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go index d410bc461f4..04febe03a86 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go @@ -27,7 +27,6 @@ import ( "time" "github.com/evanphx/json-patch" - apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,6 +38,7 @@ import ( "k8s.io/apimachinery/pkg/util/diff" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/apis/example" examplev1 "k8s.io/apiserver/pkg/apis/example/v1" "k8s.io/apiserver/pkg/endpoints/request" @@ -149,10 +149,10 @@ func TestJSONPatch(t *testing.T) { }, } { p := &patcher{ - patchType: types.JSONPatchType, - patchJS: []byte(test.patch), + patchType: types.JSONPatchType, + patchBytes: []byte(test.patch), } - jp := jsonPatcher{p} + jp := jsonPatcher{patcher: p} codec := codecs.LegacyCodec(examplev1.SchemeGroupVersion) pod := &examplev1.Pod{} pod.Name = "podA" @@ -454,12 +454,12 @@ func (tc *patchTestCase) Run(t *testing.T) { restPatcher: testPatcher, name: name, patchType: patchType, - patchJS: patch, + patchBytes: patch, trace: utiltrace.New("Patch" + name), } - resultObj, err := p.patchResource(ctx) + resultObj, _, err := p.patchResource(ctx, RequestScope{}) if len(tc.expectedError) != 0 { if err == nil || err.Error() != tc.expectedError { t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err) @@ -795,6 +795,9 @@ func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) { tc.Run(t) } +// TODO: Add test case for "apply with existing uid" verify it gives a conflict error, +// not a creation or an authz creation forbidden message + func TestHasUID(t *testing.T) { testcases := []struct { obj runtime.Object @@ -939,3 +942,11 @@ func setTcPod(tcPod *example.Pod, name string, namespace string, uid types.UID, tcPod.Spec.NodeName = nodeName } } + +func (f mutateObjectUpdateFunc) Handles(operation admission.Operation) bool { + return true +} + +func (f mutateObjectUpdateFunc) Admit(a admission.Attributes) (err error) { + return f(a.GetObject(), a.GetOldObject()) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go index 9080b9b42b3..e78346c5b0a 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go @@ -121,7 +121,15 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac } userInfo, _ := request.UserFrom(ctx) - var transformers []rest.TransformFunc + transformers := []rest.TransformFunc{} + if scope.FieldManager != nil { + transformers = append(transformers, func(_ context.Context, liveObj, newObj runtime.Object) (runtime.Object, error) { + if obj, err = scope.FieldManager.Update(liveObj, newObj, "update"); err != nil { + return nil, fmt.Errorf("failed to update object managed fields: %v", err) + } + return obj, nil + }) + } if mutatingAdmission, ok := admit.(admission.MutationInterface); ok { transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) { isNotZeroObject, err := hasUID(oldObj) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go old mode 100755 new mode 100644 diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go index 89f1caa97d5..4b9650b5575 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go @@ -27,7 +27,6 @@ import ( "unicode" restful "github.com/emicklei/go-restful" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" @@ -35,10 +34,13 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/endpoints/handlers" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/metrics" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/rest" genericfilters "k8s.io/apiserver/pkg/server/filters" + utilfeature "k8s.io/apiserver/pkg/util/feature" ) const ( @@ -264,6 +266,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag if err != nil { return nil, err } + versionedPatchOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("PatchOptions")) + if err != nil { + return nil, err + } versionedUpdateOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("UpdateOptions")) if err != nil { return nil, err @@ -511,7 +517,19 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag if a.group.MetaGroupVersion != nil { reqScope.MetaGroupVersion = *a.group.MetaGroupVersion } - reqScope.OpenAPIModels = a.group.OpenAPIModels + if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) { + fm, err := fieldmanager.NewFieldManager( + a.group.OpenAPIModels, + a.group.UnsafeConvertor, + a.group.Defaulter, + fqKindToRegister.GroupVersion(), + reqScope.HubGroupVersion, + ) + if err != nil { + return nil, fmt.Errorf("failed to create field manager: %v", err) + } + reqScope.FieldManager = fm + } for _, action := range actions { producedObject := storageMeta.ProducesObject(action.Verb) if producedObject == nil { @@ -671,17 +689,20 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag string(types.MergePatchType), string(types.StrategicMergePatchType), } + if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) { + supportedTypes = append(supportedTypes, string(types.ApplyPatchType)) + } handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulPatchResource(patcher, reqScope, admit, supportedTypes)) route := ws.PATCH(action.Path).To(handler). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). - Consumes(string(types.JSONPatchType), string(types.MergePatchType), string(types.StrategicMergePatchType)). + Consumes(supportedTypes...). Operation("patch"+namespaced+kind+strings.Title(subresource)+operationSuffix). Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). Returns(http.StatusOK, "OK", producedObject). Reads(metav1.Patch{}). Writes(producedObject) - if err := addObjectParams(ws, route, versionedUpdateOptions); err != nil { + if err := addObjectParams(ws, route, versionedPatchOptions); err != nil { return nil, err } addParams(route, action.Params) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go index 6d0475c773d..a630fd4e058 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go @@ -25,7 +25,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapitesting "k8s.io/apiserver/pkg/endpoints/testing" + genericfeatures "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/rest" + utilfeature "k8s.io/apiserver/pkg/util/feature" + utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" ) func TestPatch(t *testing.T) { @@ -69,6 +72,58 @@ func TestPatch(t *testing.T) { } } +func TestForbiddenForceOnNonApply(t *testing.T) { + storage := map[string]rest.Storage{} + ID := "id" + item := &genericapitesting.Simple{ + ObjectMeta: metav1.ObjectMeta{ + Name: ID, + Namespace: "", // update should allow the client to send an empty namespace + UID: "uid", + }, + Other: "bar", + } + simpleStorage := SimpleRESTStorage{item: *item} + storage["simple"] = &simpleStorage + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + ID, + name: ID, + namespace: metav1.NamespaceDefault, + } + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("PATCH", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader([]byte(`{"labels":{"foo":"bar"}}`))) + request.Header.Set("Content-Type", "application/merge-patch+json; charset=UTF-8") + _, err = client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + request, err = http.NewRequest("PATCH", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?force=true", bytes.NewReader([]byte(`{"labels":{"foo":"bar"}}`))) + request.Header.Set("Content-Type", "application/merge-patch+json; charset=UTF-8") + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusUnprocessableEntity { + t.Errorf("Unexpected response %#v", response) + } + + request, err = http.NewRequest("PATCH", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?force=false", bytes.NewReader([]byte(`{"labels":{"foo":"bar"}}`))) + request.Header.Set("Content-Type", "application/merge-patch+json; charset=UTF-8") + response, err = client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusUnprocessableEntity { + t.Errorf("Unexpected response %#v", response) + } +} + func TestPatchRequiresMatchingName(t *testing.T) { storage := map[string]rest.Storage{} ID := "id" @@ -97,3 +152,124 @@ func TestPatchRequiresMatchingName(t *testing.T) { t.Errorf("Unexpected response %#v", response) } } + +func TestPatchApply(t *testing.T) { + t.Skip("apply is being refactored") + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + storage := map[string]rest.Storage{} + item := &genericapitesting.Simple{ + ObjectMeta: metav1.ObjectMeta{ + Name: "id", + Namespace: "", + UID: "uid", + }, + Other: "bar", + } + simpleStorage := SimpleRESTStorage{item: *item} + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + defer server.Close() + + client := http.Client{} + request, err := http.NewRequest( + "PATCH", + server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", + bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)), + ) + request.Header.Set("Content-Type", "application/apply-patch+yaml") + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + t.Errorf("Unexpected response %#v", response) + } + if simpleStorage.updated.Labels["test"] != "yes" { + t.Errorf(`Expected labels to have "test": "yes", found %q`, simpleStorage.updated.Labels["test"]) + } + if simpleStorage.updated.Other != "bar" { + t.Errorf(`Merge should have kept initial "bar" value for Other: %v`, simpleStorage.updated.Other) + } + if _, ok := simpleStorage.updated.ObjectMeta.ManagedFields["default"]; !ok { + t.Errorf(`Expected managedFields field to be set, but is empty`) + } +} + +func TestApplyAddsGVK(t *testing.T) { + t.Skip("apply is being refactored") + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + storage := map[string]rest.Storage{} + item := &genericapitesting.Simple{ + ObjectMeta: metav1.ObjectMeta{ + Name: "id", + Namespace: "", + UID: "uid", + }, + Other: "bar", + } + simpleStorage := SimpleRESTStorage{item: *item} + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + defer server.Close() + + client := http.Client{} + request, err := http.NewRequest( + "PATCH", + server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", + bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)), + ) + request.Header.Set("Content-Type", "application/apply-patch+yaml") + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + t.Errorf("Unexpected response %#v", response) + } + // TODO: Need to fix this + expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}` + if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected { + t.Errorf( + `Expected managedFields field to be %q, got %q`, + expected, + simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion, + ) + } +} + +func TestApplyCreatesWithManagedFields(t *testing.T) { + t.Skip("apply is being refactored") + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + defer server.Close() + + client := http.Client{} + request, err := http.NewRequest( + "PATCH", + server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", + bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)), + ) + request.Header.Set("Content-Type", "application/apply-patch+yaml") + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + t.Errorf("Unexpected response %#v", response) + } + // TODO: Need to fix this + expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}` + if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected { + t.Errorf( + `Expected managedFields field to be %q, got %q`, + expected, + simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion, + ) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go index ff4aa704449..966e91ac97f 100644 --- a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go @@ -82,6 +82,12 @@ const ( // validation, merging, mutation can be tested without // committing. DryRun utilfeature.Feature = "DryRun" + + // owner: @apelisse, @lavalamp + // alpha: v1.11 + // + // Server-side apply. Merging happens on the server. + ServerSideApply utilfeature.Feature = "ServerSideApply" ) func init() { @@ -99,4 +105,5 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha}, APIListChunking: {Default: true, PreRelease: utilfeature.Beta}, DryRun: {Default: true, PreRelease: utilfeature.Beta}, + ServerSideApply: {Default: false, PreRelease: utilfeature.Alpha}, } diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go index 8e69cb76b65..7d0c4a1834a 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go @@ -28,7 +28,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" ) // RESTCreateStrategy defines the minimum validation, accepted input, and @@ -92,6 +94,12 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime. // Initializers are a deprecated alpha field and should not be saved objectMeta.SetInitializers(nil) + + // Ensure managedFields is not set unless the feature is enabled + if !utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) { + objectMeta.SetManagedFields(nil) + } + // ClusterName is ignored and should not be saved if len(objectMeta.GetClusterName()) > 0 { objectMeta.SetClusterName("") diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go index 048c35fa4ef..290b016f427 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go @@ -28,6 +28,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" ) // RESTUpdateStrategy defines the minimum validation, accepted input, and @@ -106,6 +108,12 @@ func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old run oldMeta.SetInitializers(nil) objectMeta.SetInitializers(nil) + // Ensure managedFields state is removed unless ServerSideApply is enabled + if !utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) { + oldMeta.SetManagedFields(nil) + objectMeta.SetManagedFields(nil) + } + strategy.PrepareForUpdate(ctx, obj, old) // ClusterName is ignored and should not be saved diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index da3c6c04584..3974bf9b5e9 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -324,11 +324,7 @@ func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}) error { } // installAPIResources is a private method for installing the REST storage backing each api groupversionresource -func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error { - openAPIGroupModels, err := s.getOpenAPIModelsForGroup(apiPrefix, apiGroupInfo) - if err != nil { - return fmt.Errorf("unable to get openapi models for group %v: %v", apiPrefix, err) - } +func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels openapiproto.Models) error { for _, groupVersion := range apiGroupInfo.PrioritizedVersions { if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 { klog.Warningf("Skipping API %v because it has no resources.", groupVersion) @@ -339,7 +335,7 @@ func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *A if apiGroupInfo.OptionsExternalVersion != nil { apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion } - apiGroupVersion.OpenAPIModels = openAPIGroupModels + apiGroupVersion.OpenAPIModels = openAPIModels if err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer); err != nil { return fmt.Errorf("unable to setup API %v: %v", apiGroupInfo, err) @@ -353,7 +349,13 @@ func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo if !s.legacyAPIGroupPrefixes.Has(apiPrefix) { return fmt.Errorf("%q is not in the allowed legacy API prefixes: %v", apiPrefix, s.legacyAPIGroupPrefixes.List()) } - if err := s.installAPIResources(apiPrefix, apiGroupInfo); err != nil { + + openAPIModels, err := s.getOpenAPIModels(apiPrefix, apiGroupInfo) + if err != nil { + return fmt.Errorf("unable to get openapi models: %v", err) + } + + if err := s.installAPIResources(apiPrefix, apiGroupInfo, openAPIModels); err != nil { return err } @@ -364,49 +366,62 @@ func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo return nil } +// Exposes given api groups in the API. +func (s *GenericAPIServer) InstallAPIGroups(apiGroupInfos ...*APIGroupInfo) error { + for _, apiGroupInfo := range apiGroupInfos { + // Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned. + // Catching these here places the error much closer to its origin + if len(apiGroupInfo.PrioritizedVersions[0].Group) == 0 { + return fmt.Errorf("cannot register handler with an empty group for %#v", *apiGroupInfo) + } + if len(apiGroupInfo.PrioritizedVersions[0].Version) == 0 { + return fmt.Errorf("cannot register handler with an empty version for %#v", *apiGroupInfo) + } + } + + openAPIModels, err := s.getOpenAPIModels(APIGroupPrefix, apiGroupInfos...) + if err != nil { + return fmt.Errorf("unable to get openapi models: %v", err) + } + + for _, apiGroupInfo := range apiGroupInfos { + if err := s.installAPIResources(APIGroupPrefix, apiGroupInfo, openAPIModels); err != nil { + return fmt.Errorf("unable to install api resources: %v", err) + } + + // setup discovery + // Install the version handler. + // Add a handler at /apis/ to enumerate all versions supported by this group. + apiVersionsForDiscovery := []metav1.GroupVersionForDiscovery{} + for _, groupVersion := range apiGroupInfo.PrioritizedVersions { + // Check the config to make sure that we elide versions that don't have any resources + if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 { + continue + } + apiVersionsForDiscovery = append(apiVersionsForDiscovery, metav1.GroupVersionForDiscovery{ + GroupVersion: groupVersion.String(), + Version: groupVersion.Version, + }) + } + preferredVersionForDiscovery := metav1.GroupVersionForDiscovery{ + GroupVersion: apiGroupInfo.PrioritizedVersions[0].String(), + Version: apiGroupInfo.PrioritizedVersions[0].Version, + } + apiGroup := metav1.APIGroup{ + Name: apiGroupInfo.PrioritizedVersions[0].Group, + Versions: apiVersionsForDiscovery, + PreferredVersion: preferredVersionForDiscovery, + } + + s.DiscoveryGroupManager.AddGroup(apiGroup) + s.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(s.Serializer, apiGroup).WebService()) + } + return nil +} + // Exposes the given api group in the API. func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error { - // Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned. - // Catching these here places the error much closer to its origin - if len(apiGroupInfo.PrioritizedVersions[0].Group) == 0 { - return fmt.Errorf("cannot register handler with an empty group for %#v", *apiGroupInfo) - } - if len(apiGroupInfo.PrioritizedVersions[0].Version) == 0 { - return fmt.Errorf("cannot register handler with an empty version for %#v", *apiGroupInfo) - } - - if err := s.installAPIResources(APIGroupPrefix, apiGroupInfo); err != nil { - return err - } - - // setup discovery - // Install the version handler. - // Add a handler at /apis/ to enumerate all versions supported by this group. - apiVersionsForDiscovery := []metav1.GroupVersionForDiscovery{} - for _, groupVersion := range apiGroupInfo.PrioritizedVersions { - // Check the config to make sure that we elide versions that don't have any resources - if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 { - continue - } - apiVersionsForDiscovery = append(apiVersionsForDiscovery, metav1.GroupVersionForDiscovery{ - GroupVersion: groupVersion.String(), - Version: groupVersion.Version, - }) - } - preferredVersionForDiscovery := metav1.GroupVersionForDiscovery{ - GroupVersion: apiGroupInfo.PrioritizedVersions[0].String(), - Version: apiGroupInfo.PrioritizedVersions[0].Version, - } - apiGroup := metav1.APIGroup{ - Name: apiGroupInfo.PrioritizedVersions[0].Group, - Versions: apiVersionsForDiscovery, - PreferredVersion: preferredVersionForDiscovery, - } - - s.DiscoveryGroupManager.AddGroup(apiGroup) - s.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(s.Serializer, apiGroup).WebService()) - - return nil + return s.InstallAPIGroups(apiGroupInfo) } func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion schema.GroupVersion, apiPrefix string) *genericapi.APIGroupVersion { @@ -455,12 +470,31 @@ func NewDefaultAPIGroupInfo(group string, scheme *runtime.Scheme, parameterCodec } } -// getOpenAPIModelsForGroup is a private method for getting the OpenAPI Schemas for each api group -func (s *GenericAPIServer) getOpenAPIModelsForGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) (openapiproto.Models, error) { +// getOpenAPIModels is a private method for getting the OpenAPI models +func (s *GenericAPIServer) getOpenAPIModels(apiPrefix string, apiGroupInfos ...*APIGroupInfo) (openapiproto.Models, error) { if s.openAPIConfig == nil { return nil, nil } pathsToIgnore := openapiutil.NewTrie(s.openAPIConfig.IgnorePrefixes) + resourceNames := make([]string, 0) + for _, apiGroupInfo := range apiGroupInfos { + groupResources, err := getResourceNamesForGroup(apiPrefix, apiGroupInfo, pathsToIgnore) + if err != nil { + return nil, err + } + resourceNames = append(resourceNames, groupResources...) + } + + // Build the openapi definitions for those resources and convert it to proto models + openAPISpec, err := openapibuilder.BuildOpenAPIDefinitionsForResources(s.openAPIConfig, resourceNames...) + if err != nil { + return nil, err + } + return utilopenapi.ToProtoModels(openAPISpec) +} + +// getResourceNamesForGroup is a private method for getting the canonical names for each resource to build in an api group +func getResourceNamesForGroup(apiPrefix string, apiGroupInfo *APIGroupInfo, pathsToIgnore openapiutil.Trie) ([]string, error) { // Get the canonical names of every resource we need to build in this api group resourceNames := make([]string, 0) for _, groupVersion := range apiGroupInfo.PrioritizedVersions { @@ -481,10 +515,5 @@ func (s *GenericAPIServer) getOpenAPIModelsForGroup(apiPrefix string, apiGroupIn } } - // Build the openapi definitions for those resources and convert it to proto models - openAPISpec, err := openapibuilder.BuildOpenAPIDefinitionsForResources(s.openAPIConfig, resourceNames...) - if err != nil { - return nil, err - } - return utilopenapi.ToProtoModels(openAPISpec) + return resourceNames, nil } diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go index 059d518af22..851351cf166 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go @@ -138,9 +138,9 @@ func (m *Helper) createResource(c RESTClient, resource, namespace string, obj ru Do(). Get() } -func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte, options *metav1.UpdateOptions) (runtime.Object, error) { +func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte, options *metav1.PatchOptions) (runtime.Object, error) { if options == nil { - options = &metav1.UpdateOptions{} + options = &metav1.PatchOptions{} } return m.RESTClient.Patch(pt). NamespaceIfScoped(namespace, m.NamespaceScoped). diff --git a/staging/src/k8s.io/client-go/deprecated-dynamic/client.go b/staging/src/k8s.io/client-go/deprecated-dynamic/client.go index 3b8efffab6b..bcbfed706f4 100644 --- a/staging/src/k8s.io/client-go/deprecated-dynamic/client.go +++ b/staging/src/k8s.io/client-go/deprecated-dynamic/client.go @@ -127,5 +127,5 @@ func (s oldResourceShimType) List(opts metav1.ListOptions) (runtime.Object, erro } func (s oldResourceShimType) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) { - return s.ResourceInterface.Patch(name, pt, data, metav1.UpdateOptions{}, s.subresources...) + return s.ResourceInterface.Patch(name, pt, data, metav1.PatchOptions{}, s.subresources...) } diff --git a/staging/src/k8s.io/client-go/dynamic/client_test.go b/staging/src/k8s.io/client-go/dynamic/client_test.go index e74cb832a06..0edf3265f25 100644 --- a/staging/src/k8s.io/client-go/dynamic/client_test.go +++ b/staging/src/k8s.io/client-go/dynamic/client_test.go @@ -638,7 +638,7 @@ func TestPatch(t *testing.T) { } defer srv.Close() - got, err := cl.Resource(resource).Namespace(tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch, metav1.UpdateOptions{}, tc.subresource...) + got, err := cl.Resource(resource).Namespace(tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch, metav1.PatchOptions{}, tc.subresource...) if err != nil { t.Errorf("unexpected error when patching %q: %v", tc.name, err) continue diff --git a/staging/src/k8s.io/client-go/dynamic/fake/simple.go b/staging/src/k8s.io/client-go/dynamic/fake/simple.go index 6ce3b9b415b..819b8d9c90c 100644 --- a/staging/src/k8s.io/client-go/dynamic/fake/simple.go +++ b/staging/src/k8s.io/client-go/dynamic/fake/simple.go @@ -333,7 +333,7 @@ func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, } // TODO: opts are currently ignored. -func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) { +func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) { var uncastRet runtime.Object var err error switch { diff --git a/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go b/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go index f099195646e..183420fcd01 100644 --- a/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go +++ b/staging/src/k8s.io/client-go/dynamic/fake/simple_test.go @@ -96,7 +96,7 @@ func (tc *patchTestCase) runner(t *testing.T) { client := NewSimpleDynamicClient(runtime.NewScheme(), tc.object) resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace) - got, recErr := resourceInterface.Patch(testName, tc.patchType, tc.patchBytes, metav1.UpdateOptions{}) + got, recErr := resourceInterface.Patch(testName, tc.patchType, tc.patchBytes, metav1.PatchOptions{}) if err := tc.verifyErr(recErr); err != nil { t.Error(err) diff --git a/staging/src/k8s.io/client-go/dynamic/interface.go b/staging/src/k8s.io/client-go/dynamic/interface.go index c457be1780b..70756a4f588 100644 --- a/staging/src/k8s.io/client-go/dynamic/interface.go +++ b/staging/src/k8s.io/client-go/dynamic/interface.go @@ -37,7 +37,7 @@ type ResourceInterface interface { Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) Watch(opts metav1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) + Patch(name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) } type NamespaceableResourceInterface interface { diff --git a/staging/src/k8s.io/client-go/dynamic/simple.go b/staging/src/k8s.io/client-go/dynamic/simple.go index 9e21cda6e37..852f0c5120a 100644 --- a/staging/src/k8s.io/client-go/dynamic/simple.go +++ b/staging/src/k8s.io/client-go/dynamic/simple.go @@ -283,7 +283,7 @@ func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, WatchWithSpecificDecoders(wrappedDecoderFn, unstructured.UnstructuredJSONScheme) } -func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) { +func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) { result := c.client.client. Patch(pt). AbsPath(append(c.makeURLSegments(name), subresources...)...). diff --git a/staging/src/k8s.io/cluster-bootstrap/Godeps/Godeps.json b/staging/src/k8s.io/cluster-bootstrap/Godeps/Godeps.json index 4c402fdc979..e200cf466a5 100644 --- a/staging/src/k8s.io/cluster-bootstrap/Godeps/Godeps.json +++ b/staging/src/k8s.io/cluster-bootstrap/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "golang.org/x/net/http2", diff --git a/staging/src/k8s.io/component-base/Godeps/Godeps.json b/staging/src/k8s.io/component-base/Godeps/Godeps.json index 9ad4bcc8e77..30dabce53ad 100644 --- a/staging/src/k8s.io/component-base/Godeps/Godeps.json +++ b/staging/src/k8s.io/component-base/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/spf13/pflag", diff --git a/staging/src/k8s.io/csi-translation-lib/Godeps/Godeps.json b/staging/src/k8s.io/csi-translation-lib/Godeps/Godeps.json index 22fc5282f46..e3385165391 100644 --- a/staging/src/k8s.io/csi-translation-lib/Godeps/Godeps.json +++ b/staging/src/k8s.io/csi-translation-lib/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "golang.org/x/net/http2", diff --git a/staging/src/k8s.io/kube-controller-manager/Godeps/Godeps.json b/staging/src/k8s.io/kube-controller-manager/Godeps/Godeps.json index fd72952019d..6be6fb20f1b 100644 --- a/staging/src/k8s.io/kube-controller-manager/Godeps/Godeps.json +++ b/staging/src/k8s.io/kube-controller-manager/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "golang.org/x/net/http2", diff --git a/staging/src/k8s.io/kube-proxy/Godeps/Godeps.json b/staging/src/k8s.io/kube-proxy/Godeps/Godeps.json index 538d561a2b3..0646f99f054 100644 --- a/staging/src/k8s.io/kube-proxy/Godeps/Godeps.json +++ b/staging/src/k8s.io/kube-proxy/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "golang.org/x/net/http2", diff --git a/staging/src/k8s.io/kube-scheduler/Godeps/Godeps.json b/staging/src/k8s.io/kube-scheduler/Godeps/Godeps.json index 5e62cd9764d..d94682be86a 100644 --- a/staging/src/k8s.io/kube-scheduler/Godeps/Godeps.json +++ b/staging/src/k8s.io/kube-scheduler/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "golang.org/x/net/http2", diff --git a/staging/src/k8s.io/kubelet/Godeps/Godeps.json b/staging/src/k8s.io/kubelet/Godeps/Godeps.json index e2933e6472b..66be2bfe7bb 100644 --- a/staging/src/k8s.io/kubelet/Godeps/Godeps.json +++ b/staging/src/k8s.io/kubelet/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "golang.org/x/net/http2", diff --git a/test/integration/apiserver/BUILD b/test/integration/apiserver/BUILD index bf404be616f..e1afd14fee9 100644 --- a/test/integration/apiserver/BUILD +++ b/test/integration/apiserver/BUILD @@ -68,6 +68,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//test/integration/apiserver/apply:all-srcs", + ], tags = ["automanaged"], ) diff --git a/test/integration/apiserver/apiserver_test.go b/test/integration/apiserver/apiserver_test.go index 9ecb123f920..0086dadddd5 100644 --- a/test/integration/apiserver/apiserver_test.go +++ b/test/integration/apiserver/apiserver_test.go @@ -56,6 +56,7 @@ func setupWithResources(t *testing.T, groupVersions []schema.GroupVersion, resou resourceConfig.EnableResources(resources...) masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig } + masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig() _, s, closeFn := framework.RunAMaster(masterConfig) clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL}) diff --git a/test/integration/apiserver/apply/BUILD b/test/integration/apiserver/apply/BUILD new file mode 100644 index 00000000000..d5633a713d1 --- /dev/null +++ b/test/integration/apiserver/apply/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_test") + +go_test( + name = "go_default_test", + srcs = [ + "apply_test.go", + "main_test.go", + ], + deps = [ + "//pkg/master:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/features:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/rest:go_default_library", + "//test/integration/framework: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/test/integration/apiserver/apply/apply_test.go b/test/integration/apiserver/apply/apply_test.go new file mode 100644 index 00000000000..4bbbaf59804 --- /dev/null +++ b/test/integration/apiserver/apply/apply_test.go @@ -0,0 +1,206 @@ +/* +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 apiserver + +import ( + "net/http/httptest" + "testing" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + genericfeatures "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" + clientset "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" + "k8s.io/kubernetes/pkg/master" + "k8s.io/kubernetes/test/integration/framework" +) + +func setup(t *testing.T, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) { + masterConfig := framework.NewIntegrationTestMasterConfig() + if len(groupVersions) > 0 { + resourceConfig := master.DefaultAPIResourceConfigSource() + resourceConfig.EnableVersions(groupVersions...) + masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig + } + masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig() + _, s, closeFn := framework.RunAMaster(masterConfig) + + clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL}) + if err != nil { + t.Fatalf("Error in create clientset: %v", err) + } + return s, clientSet, closeFn +} + +// TestApplyAlsoCreates makes sure that PATCH requests with the apply content type +// will create the object if it doesn't already exist +// TODO: make a set of test cases in an easy-to-consume place (separate package?) so it's easy to test in both integration and e2e. +func TestApplyAlsoCreates(t *testing.T) { + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + + _, client, closeFn := setup(t) + defer closeFn() + + _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + Namespace("default"). + Resource("pods"). + Name("test-pod"). + Body([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-pod" + }, + "spec": { + "containers": [{ + "name": "test-container", + "image": "test-image" + }] + } + }`)). + Do(). + Get() + if err != nil { + t.Fatalf("Failed to create object using Apply patch: %v", err) + } + + _, err = client.CoreV1().RESTClient().Get().Namespace("default").Resource("pods").Name("test-pod").Do().Get() + if err != nil { + t.Fatalf("Failed to retrieve object: %v", err) + } +} + +// TestCreateOnApplyFailsWithUID makes sure that PATCH requests with the apply content type +// will not create the object if it doesn't already exist and it specifies a UID +func TestCreateOnApplyFailsWithUID(t *testing.T) { + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + + _, client, closeFn := setup(t) + defer closeFn() + + _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + Namespace("default"). + Resource("pods"). + Name("test-pod-uid"). + Body([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-pod-uid", + "uid": "88e00824-7f0e-11e8-94a1-c8d3ffb15800" + }, + "spec": { + "containers": [{ + "name": "test-container", + "image": "test-image" + }] + } + }`)). + Do(). + Get() + if !errors.IsConflict(err) { + t.Fatalf("Expected conflict error but got: %v", err) + } +} + +func TestApplyUpdateApplyConflictForced(t *testing.T) { + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + + _, client, closeFn := setup(t) + defer closeFn() + + obj := []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + "labels": {"app": "nginx"} + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "nginx:latest" + }] + } + } + } + }`) + + _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + AbsPath("/apis/apps/v1"). + Namespace("default"). + Resource("deployments"). + Name("deployment"). + Body(obj).Do().Get() + if err != nil { + t.Fatalf("Failed to create object using Apply patch: %v", err) + } + + _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). + AbsPath("/apis/extensions/v1beta1"). + Namespace("default"). + Resource("deployments"). + Name("deployment"). + Body([]byte(`{"spec":{"replicas": 5}}`)).Do().Get() + if err != nil { + t.Fatalf("Failed to patch object: %v", err) + } + + _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + AbsPath("/apis/apps/v1"). + Namespace("default"). + Resource("deployments"). + Name("deployment"). + Body([]byte(obj)).Do().Get() + if err == nil { + t.Fatalf("Expecting to get conflicts when applying object") + } + status, ok := err.(*errors.StatusError) + if !ok { + t.Fatalf("Expecting to get conflicts as API error") + } + if len(status.Status().Details.Causes) < 1 { + t.Fatalf("Expecting to get at least one conflict when applying object, got: %v", status.Status().Details.Causes) + } + + _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + AbsPath("/apis/apps/v1"). + Namespace("default"). + Resource("deployments"). + Name("deployment"). + Param("force", "true"). + Body([]byte(obj)).Do().Get() + if err != nil { + t.Fatalf("Failed to apply object with force: %v", err) + } +} diff --git a/test/integration/apiserver/apply/main_test.go b/test/integration/apiserver/apply/main_test.go new file mode 100644 index 00000000000..268a3588398 --- /dev/null +++ b/test/integration/apiserver/apply/main_test.go @@ -0,0 +1,27 @@ +/* +Copyright 2017 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 apiserver + +import ( + "testing" + + "k8s.io/kubernetes/test/integration/framework" +) + +func TestMain(m *testing.M) { + framework.EtcdMain(m.Run) +} diff --git a/test/integration/apiserver/print_test.go b/test/integration/apiserver/print_test.go index bbf8bfaf09c..7ff1d604751 100644 --- a/test/integration/apiserver/print_test.go +++ b/test/integration/apiserver/print_test.go @@ -62,6 +62,7 @@ var kindWhiteList = sets.NewString( "ListOptions", "CreateOptions", "UpdateOptions", + "PatchOptions", "NodeProxyOptions", "PodAttachOptions", "PodExecOptions", diff --git a/test/integration/auth/BUILD b/test/integration/auth/BUILD index 4a537465fdd..cd8db061f9e 100644 --- a/test/integration/auth/BUILD +++ b/test/integration/auth/BUILD @@ -72,6 +72,7 @@ go_test( "//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library", diff --git a/test/integration/auth/rbac_test.go b/test/integration/auth/rbac_test.go index effffb12228..963f872c86f 100644 --- a/test/integration/auth/rbac_test.go +++ b/test/integration/auth/rbac_test.go @@ -27,8 +27,6 @@ import ( "testing" "time" - "k8s.io/klog" - apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -37,12 +35,16 @@ import ( "k8s.io/apiserver/pkg/authentication/token/tokenfile" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" + genericfeatures "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/generic" + utilfeature "k8s.io/apiserver/pkg/util/feature" + utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" externalclientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" watchtools "k8s.io/client-go/tools/watch" "k8s.io/client-go/transport" csiclientset "k8s.io/csi-api/pkg/client/clientset/versioned" + "k8s.io/klog" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" api "k8s.io/kubernetes/pkg/apis/core" @@ -292,6 +294,8 @@ var ( ) func TestRBAC(t *testing.T) { + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + superUser := "admin/system:masters" tests := []struct { @@ -478,6 +482,40 @@ func TestRBAC(t *testing.T) { {"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, }, }, + // { + // bootstrapRoles: bootstrapRoles{ + // clusterRoles: []rbacapi.ClusterRole{ + // { + // ObjectMeta: metav1.ObjectMeta{Name: "allow-all"}, + // Rules: []rbacapi.PolicyRule{ruleAllowAll}, + // }, + // { + // ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, + // Rules: []rbacapi.PolicyRule{ + // rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(), + // }, + // }, + // }, + // clusterRoleBindings: []rbacapi.ClusterRoleBinding{ + // { + // ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, + // Subjects: []rbacapi.Subject{ + // {Kind: "User", Name: "limitrange-patcher"}, + // }, + // RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"}, + // }, + // }, + // }, + // requests: []request{ + // // Create the namespace used later in the test + // {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated}, + + // {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden}, + // {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated}, + // {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, + // {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, + // }, + // }, } for i, tc := range tests { @@ -494,6 +532,7 @@ func TestRBAC(t *testing.T) { "nonescalating-rolebinding-writer": {Name: "nonescalating-rolebinding-writer"}, "pod-reader": {Name: "pod-reader"}, "limitrange-updater": {Name: "limitrange-updater"}, + "limitrange-patcher": {Name: "limitrange-patcher"}, "user-with-no-permissions": {Name: "user-with-no-permissions"}, })) _, s, closeFn := framework.RunAMaster(masterConfig) @@ -530,6 +569,12 @@ func TestRBAC(t *testing.T) { } req, err := http.NewRequest(r.verb, s.URL+path, body) + // TODO: Un-comment this when Apply works again + // if r.verb == "PATCH" { + // // For patch operations, use the apply content type + // req.Header.Add("Content-Type", string(types.ApplyPatchType)) + // } + if err != nil { t.Fatalf("failed to create request: %v", err) } diff --git a/test/integration/dryrun/dryrun_test.go b/test/integration/dryrun/dryrun_test.go index 99e039d3539..4d3fd130ae3 100644 --- a/test/integration/dryrun/dryrun_test.go +++ b/test/integration/dryrun/dryrun_test.go @@ -58,7 +58,7 @@ func DryRunCreateTest(t *testing.T, rsc dynamic.ResourceInterface, obj *unstruct func DryRunPatchTest(t *testing.T, rsc dynamic.ResourceInterface, name string) { patch := []byte(`{"metadata":{"annotations":{"patch": "true"}}}`) - obj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}}) + obj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.PatchOptions{DryRun: []string{metav1.DryRunAll}}) if err != nil { t.Fatalf("failed to dry-run patch object: %v", err) } @@ -97,7 +97,7 @@ func DryRunScalePatchTest(t *testing.T, rsc dynamic.ResourceInterface, name stri replicas := getReplicasOrFail(t, obj) patch := []byte(`{"spec":{"replicas":10}}`) - patchedObj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}}, "scale") + patchedObj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.PatchOptions{DryRun: []string{metav1.DryRunAll}}, "scale") if err != nil { t.Fatalf("failed to dry-run patch object: %v", err) } diff --git a/test/integration/framework/BUILD b/test/integration/framework/BUILD index 5c485081ad1..ff697741ba6 100644 --- a/test/integration/framework/BUILD +++ b/test/integration/framework/BUILD @@ -67,6 +67,7 @@ go_library( "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/pborman/uuid:go_default_library", "//vendor/k8s.io/klog:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/common:go_default_library", ], ) diff --git a/test/integration/framework/master_utils.go b/test/integration/framework/master_utils.go index 95b7d3d8378..ad376fe4860 100644 --- a/test/integration/framework/master_utils.go +++ b/test/integration/framework/master_utils.go @@ -27,8 +27,6 @@ import ( "github.com/go-openapi/spec" "github.com/pborman/uuid" - "k8s.io/klog" - apps "k8s.io/api/apps/v1beta1" auditreg "k8s.io/api/auditregistration/v1alpha1" autoscaling "k8s.io/api/autoscaling/v1" @@ -55,6 +53,8 @@ import ( "k8s.io/client-go/informers" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" + "k8s.io/klog" + openapicommon "k8s.io/kube-openapi/pkg/common" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/batch" @@ -109,6 +109,24 @@ func (h *MasterHolder) SetMaster(m *master.Master) { close(h.Initialized) } +func DefaultOpenAPIConfig() *openapicommon.Config { + openAPIConfig := genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme)) + openAPIConfig.Info = &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Kubernetes", + Version: "unversioned", + }, + } + openAPIConfig.DefaultResponse = &spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "Default Response.", + }, + } + openAPIConfig.GetDefinitions = openapi.GetOpenAPIDefinitions + + return openAPIConfig +} + // startMasterOrDie starts a kubernetes master and an httpserver to handle api requests func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Server, masterReceiver MasterReceiver) (*master.Master, *httptest.Server, CloseFunc) { var m *master.Master @@ -138,19 +156,7 @@ func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Serv if masterConfig == nil { masterConfig = NewMasterConfig() - masterConfig.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme)) - masterConfig.GenericConfig.OpenAPIConfig.Info = &spec.Info{ - InfoProps: spec.InfoProps{ - Title: "Kubernetes", - Version: "unversioned", - }, - } - masterConfig.GenericConfig.OpenAPIConfig.DefaultResponse = &spec.Response{ - ResponseProps: spec.ResponseProps{ - Description: "Default Response.", - }, - } - masterConfig.GenericConfig.OpenAPIConfig.GetDefinitions = openapi.GetOpenAPIDefinitions + masterConfig.GenericConfig.OpenAPIConfig = DefaultOpenAPIConfig() } // set the loopback client config From 7e4cc38401542d0eb1af90054103852347478e9f Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Wed, 16 Jan 2019 21:14:58 -0800 Subject: [PATCH 03/10] Random unrelated vendor --- vendor/BUILD | 6 + vendor/github.com/google/gofuzz/fuzz.go | 96 ++++--- .../k8s.io/kube-openapi/pkg/schemaconv/BUILD | 27 ++ .../k8s.io/kube-openapi/pkg/schemaconv/smd.go | 242 ++++++++++++++++++ 4 files changed, 340 insertions(+), 31 deletions(-) create mode 100644 vendor/k8s.io/kube-openapi/pkg/schemaconv/BUILD create mode 100644 vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go diff --git a/vendor/BUILD b/vendor/BUILD index f69a079edb1..fea97a1485d 100644 --- a/vendor/BUILD +++ b/vendor/BUILD @@ -467,6 +467,7 @@ filegroup( "//vendor/k8s.io/kube-openapi/pkg/common:all-srcs", "//vendor/k8s.io/kube-openapi/pkg/generators:all-srcs", "//vendor/k8s.io/kube-openapi/pkg/handler:all-srcs", + "//vendor/k8s.io/kube-openapi/pkg/schemaconv:all-srcs", "//vendor/k8s.io/kube-openapi/pkg/util:all-srcs", "//vendor/k8s.io/repo-infra/kazel:all-srcs", "//vendor/k8s.io/utils/buffer:all-srcs", @@ -481,6 +482,11 @@ filegroup( "//vendor/k8s.io/utils/pointer:all-srcs", "//vendor/k8s.io/utils/strings:all-srcs", "//vendor/k8s.io/utils/trace:all-srcs", + "//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:all-srcs", + "//vendor/sigs.k8s.io/structured-merge-diff/merge:all-srcs", + "//vendor/sigs.k8s.io/structured-merge-diff/schema:all-srcs", + "//vendor/sigs.k8s.io/structured-merge-diff/typed:all-srcs", + "//vendor/sigs.k8s.io/structured-merge-diff/value:all-srcs", "//vendor/sigs.k8s.io/yaml:all-srcs", "//vendor/vbom.ml/util/sortorder:all-srcs", ], diff --git a/vendor/github.com/google/gofuzz/fuzz.go b/vendor/github.com/google/gofuzz/fuzz.go index 4f888fbc8fa..1dfa80a6fca 100644 --- a/vendor/github.com/google/gofuzz/fuzz.go +++ b/vendor/github.com/google/gofuzz/fuzz.go @@ -34,21 +34,27 @@ type Fuzzer struct { nilChance float64 minElements int maxElements int + maxDepth int } // New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs, // RandSource, NilChance, or NumElements in any order. func New() *Fuzzer { + return NewWithSeed(time.Now().UnixNano()) +} + +func NewWithSeed(seed int64) *Fuzzer { f := &Fuzzer{ defaultFuzzFuncs: fuzzFuncMap{ reflect.TypeOf(&time.Time{}): reflect.ValueOf(fuzzTime), }, fuzzFuncs: fuzzFuncMap{}, - r: rand.New(rand.NewSource(time.Now().UnixNano())), + r: rand.New(rand.NewSource(seed)), nilChance: .2, minElements: 1, maxElements: 10, + maxDepth: 100, } return f } @@ -136,6 +142,14 @@ func (f *Fuzzer) genShouldFill() bool { return f.r.Float64() > f.nilChance } +// MaxDepth sets the maximum number of recursive fuzz calls that will be made +// before stopping. This includes struct members, pointers, and map and slice +// elements. +func (f *Fuzzer) MaxDepth(d int) *Fuzzer { + f.maxDepth = d + return f +} + // Fuzz recursively fills all of obj's fields with something random. First // this tries to find a custom fuzz function (see Funcs). If there is no // custom function this tests whether the object implements fuzz.Interface and, @@ -144,17 +158,19 @@ func (f *Fuzzer) genShouldFill() bool { // fails, this will generate random values for all primitive fields and then // recurse for all non-primitives. // -// Not safe for cyclic or tree-like structs! +// This is safe for cyclic or tree-like structs, up to a limit. Use the +// MaxDepth method to adjust how deep you need it to recurse. // -// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ ) -// Intended for tests, so will panic on bad input or unimplemented fields. +// obj must be a pointer. Only exported (public) fields can be set (thanks, +// golang :/ ) Intended for tests, so will panic on bad input or unimplemented +// fields. func (f *Fuzzer) Fuzz(obj interface{}) { v := reflect.ValueOf(obj) if v.Kind() != reflect.Ptr { panic("needed ptr!") } v = v.Elem() - f.doFuzz(v, 0) + f.fuzzWithContext(v, 0) } // FuzzNoCustom is just like Fuzz, except that any custom fuzz function for @@ -170,7 +186,7 @@ func (f *Fuzzer) FuzzNoCustom(obj interface{}) { panic("needed ptr!") } v = v.Elem() - f.doFuzz(v, flagNoCustomFuzz) + f.fuzzWithContext(v, flagNoCustomFuzz) } const ( @@ -178,69 +194,87 @@ const ( flagNoCustomFuzz uint64 = 1 << iota ) -func (f *Fuzzer) doFuzz(v reflect.Value, flags uint64) { +func (f *Fuzzer) fuzzWithContext(v reflect.Value, flags uint64) { + fc := &fuzzerContext{fuzzer: f} + fc.doFuzz(v, flags) +} + +// fuzzerContext carries context about a single fuzzing run, which lets Fuzzer +// be thread-safe. +type fuzzerContext struct { + fuzzer *Fuzzer + curDepth int +} + +func (fc *fuzzerContext) doFuzz(v reflect.Value, flags uint64) { + if fc.curDepth >= fc.fuzzer.maxDepth { + return + } + fc.curDepth++ + defer func() { fc.curDepth-- }() + if !v.CanSet() { return } if flags&flagNoCustomFuzz == 0 { // Check for both pointer and non-pointer custom functions. - if v.CanAddr() && f.tryCustom(v.Addr()) { + if v.CanAddr() && fc.tryCustom(v.Addr()) { return } - if f.tryCustom(v) { + if fc.tryCustom(v) { return } } if fn, ok := fillFuncMap[v.Kind()]; ok { - fn(v, f.r) + fn(v, fc.fuzzer.r) return } switch v.Kind() { case reflect.Map: - if f.genShouldFill() { + if fc.fuzzer.genShouldFill() { v.Set(reflect.MakeMap(v.Type())) - n := f.genElementCount() + n := fc.fuzzer.genElementCount() for i := 0; i < n; i++ { key := reflect.New(v.Type().Key()).Elem() - f.doFuzz(key, 0) + fc.doFuzz(key, 0) val := reflect.New(v.Type().Elem()).Elem() - f.doFuzz(val, 0) + fc.doFuzz(val, 0) v.SetMapIndex(key, val) } return } v.Set(reflect.Zero(v.Type())) case reflect.Ptr: - if f.genShouldFill() { + if fc.fuzzer.genShouldFill() { v.Set(reflect.New(v.Type().Elem())) - f.doFuzz(v.Elem(), 0) + fc.doFuzz(v.Elem(), 0) return } v.Set(reflect.Zero(v.Type())) case reflect.Slice: - if f.genShouldFill() { - n := f.genElementCount() + if fc.fuzzer.genShouldFill() { + n := fc.fuzzer.genElementCount() v.Set(reflect.MakeSlice(v.Type(), n, n)) for i := 0; i < n; i++ { - f.doFuzz(v.Index(i), 0) + fc.doFuzz(v.Index(i), 0) } return } v.Set(reflect.Zero(v.Type())) case reflect.Array: - if f.genShouldFill() { + if fc.fuzzer.genShouldFill() { n := v.Len() for i := 0; i < n; i++ { - f.doFuzz(v.Index(i), 0) + fc.doFuzz(v.Index(i), 0) } return } v.Set(reflect.Zero(v.Type())) case reflect.Struct: for i := 0; i < v.NumField(); i++ { - f.doFuzz(v.Field(i), 0) + fc.doFuzz(v.Field(i), 0) } case reflect.Chan: fallthrough @@ -255,20 +289,20 @@ func (f *Fuzzer) doFuzz(v reflect.Value, flags uint64) { // tryCustom searches for custom handlers, and returns true iff it finds a match // and successfully randomizes v. -func (f *Fuzzer) tryCustom(v reflect.Value) bool { +func (fc *fuzzerContext) tryCustom(v reflect.Value) bool { // First: see if we have a fuzz function for it. - doCustom, ok := f.fuzzFuncs[v.Type()] + doCustom, ok := fc.fuzzer.fuzzFuncs[v.Type()] if !ok { // Second: see if it can fuzz itself. if v.CanInterface() { intf := v.Interface() if fuzzable, ok := intf.(Interface); ok { - fuzzable.Fuzz(Continue{f: f, Rand: f.r}) + fuzzable.Fuzz(Continue{fc: fc, Rand: fc.fuzzer.r}) return true } } // Finally: see if there is a default fuzz function. - doCustom, ok = f.defaultFuzzFuncs[v.Type()] + doCustom, ok = fc.fuzzer.defaultFuzzFuncs[v.Type()] if !ok { return false } @@ -294,8 +328,8 @@ func (f *Fuzzer) tryCustom(v reflect.Value) bool { } doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{ - f: f, - Rand: f.r, + fc: fc, + Rand: fc.fuzzer.r, })}) return true } @@ -310,7 +344,7 @@ type Interface interface { // Continue can be passed to custom fuzzing functions to allow them to use // the correct source of randomness and to continue fuzzing their members. type Continue struct { - f *Fuzzer + fc *fuzzerContext // For convenience, Continue implements rand.Rand via embedding. // Use this for generating any randomness if you want your fuzzing @@ -325,7 +359,7 @@ func (c Continue) Fuzz(obj interface{}) { panic("needed ptr!") } v = v.Elem() - c.f.doFuzz(v, 0) + c.fc.doFuzz(v, 0) } // FuzzNoCustom continues fuzzing obj, except that any custom fuzz function for @@ -338,7 +372,7 @@ func (c Continue) FuzzNoCustom(obj interface{}) { panic("needed ptr!") } v = v.Elem() - c.f.doFuzz(v, flagNoCustomFuzz) + c.fc.doFuzz(v, flagNoCustomFuzz) } // RandString makes a random string up to 20 characters long. The returned string diff --git a/vendor/k8s.io/kube-openapi/pkg/schemaconv/BUILD b/vendor/k8s.io/kube-openapi/pkg/schemaconv/BUILD new file mode 100644 index 00000000000..e43e724f2a8 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/schemaconv/BUILD @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["smd.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/kube-openapi/pkg/schemaconv", + importpath = "k8s.io/kube-openapi/pkg/schemaconv", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + "//vendor/sigs.k8s.io/structured-merge-diff/schema: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/k8s.io/kube-openapi/pkg/schemaconv/smd.go b/vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go new file mode 100644 index 00000000000..a885527c726 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go @@ -0,0 +1,242 @@ +/* +Copyright 2017 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 schemaconv + +import ( + "errors" + "fmt" + "path" + "strings" + + "k8s.io/kube-openapi/pkg/util/proto" + "sigs.k8s.io/structured-merge-diff/schema" +) + +// ToSchema converts openapi definitions into a schema suitable for structured +// merge (i.e. kubectl apply v2). +func ToSchema(models proto.Models) (*schema.Schema, error) { + c := convert{ + input: models, + output: &schema.Schema{}, + } + if err := c.convertAll(); err != nil { + return nil, err + } + + return c.output, nil +} + +type convert struct { + input proto.Models + output *schema.Schema + + currentName string + current *schema.Atom + errorMessages []string +} + +func (c *convert) push(name string, a *schema.Atom) *convert { + return &convert{ + input: c.input, + output: c.output, + currentName: name, + current: a, + } +} + +func (c *convert) top() *schema.Atom { return c.current } + +func (c *convert) pop(c2 *convert) { + c.errorMessages = append(c.errorMessages, c2.errorMessages...) +} + +func (c *convert) convertAll() error { + for _, name := range c.input.ListModels() { + model := c.input.LookupModel(name) + c.insertTypeDef(name, model) + } + if len(c.errorMessages) > 0 { + return errors.New(strings.Join(c.errorMessages, "\n")) + } + return nil +} + +func (c *convert) reportError(format string, args ...interface{}) { + c.errorMessages = append(c.errorMessages, + c.currentName+": "+fmt.Sprintf(format, args...), + ) +} + +func (c *convert) insertTypeDef(name string, model proto.Schema) { + def := schema.TypeDef{ + Name: name, + } + c2 := c.push(name, &def.Atom) + model.Accept(c2) + c.pop(c2) + if def.Atom == (schema.Atom{}) { + // This could happen if there were a top-level reference. + return + } + c.output.Types = append(c.output.Types, def) +} + +func (c *convert) makeRef(model proto.Schema) schema.TypeRef { + var tr schema.TypeRef + if r, ok := model.(*proto.Ref); ok { + // reference a named type + _, n := path.Split(r.Reference()) + tr.NamedType = &n + } else { + // compute the type inline + c2 := c.push("inlined in "+c.currentName, &tr.Inlined) + model.Accept(c2) + c.pop(c2) + + if tr == (schema.TypeRef{}) { + // emit warning? + tr.Inlined.Untyped = &schema.Untyped{} + } + } + return tr +} + +func (c *convert) VisitKind(k *proto.Kind) { + a := c.top() + a.Struct = &schema.Struct{} + for _, name := range k.FieldOrder { + member := k.Fields[name] + tr := c.makeRef(member) + a.Struct.Fields = append(a.Struct.Fields, schema.StructField{ + Name: name, + Type: tr, + }) + } + + // TODO: Get element relationship when we start adding it to the spec. +} + +func toStringSlice(o interface{}) (out []string, ok bool) { + switch t := o.(type) { + case []interface{}: + for _, v := range t { + switch vt := v.(type) { + case string: + out = append(out, vt) + } + } + return out, true + } + return nil, false +} + +func (c *convert) VisitArray(a *proto.Array) { + atom := c.top() + atom.List = &schema.List{ + ElementRelationship: schema.Atomic, + } + l := atom.List + l.ElementType = c.makeRef(a.SubType) + + ext := a.GetExtensions() + + if val, ok := ext["x-kubernetes-list-type"]; ok { + if val == "atomic" { + l.ElementRelationship = schema.Atomic + } else if val == "set" { + l.ElementRelationship = schema.Associative + } else if val == "map" { + l.ElementRelationship = schema.Associative + if keys, ok := ext["x-kubernetes-list-map-keys"]; ok { + if keyNames, ok := toStringSlice(keys); ok { + l.Keys = keyNames + } else { + c.reportError("uninterpreted map keys: %#v", keys) + } + } else { + c.reportError("missing map keys") + } + } else { + c.reportError("unknown list type %v", val) + l.ElementRelationship = schema.Atomic + } + } else if val, ok := ext["x-kubernetes-patch-strategy"]; ok { + if val == "merge" || val == "merge,retainKeys" { + l.ElementRelationship = schema.Associative + if key, ok := ext["x-kubernetes-patch-merge-key"]; ok { + if keyName, ok := key.(string); ok { + l.Keys = []string{keyName} + } else { + c.reportError("uninterpreted merge key: %#v", key) + } + } else { + // It's not an error for this to be absent, it + // means it's a set. + } + } else if val == "retainKeys" { + } else { + c.reportError("unknown patch strategy %v", val) + l.ElementRelationship = schema.Atomic + } + } +} + +func (c *convert) VisitMap(m *proto.Map) { + a := c.top() + a.Map = &schema.Map{} + a.Map.ElementType = c.makeRef(m.SubType) + + // TODO: Get element relationship when we start putting it into the + // spec. +} + +func (c *convert) VisitPrimitive(p *proto.Primitive) { + a := c.top() + ptr := func(s schema.Scalar) *schema.Scalar { return &s } + switch p.Type { + case proto.Integer: + a.Scalar = ptr(schema.Numeric) + case proto.Number: + a.Scalar = ptr(schema.Numeric) + case proto.String: + switch p.Format { + case "": + a.Scalar = ptr(schema.String) + case "byte": + // byte really means []byte and is encoded as a string. + a.Scalar = ptr(schema.String) + case "int-or-string": + a.Untyped = &schema.Untyped{} + case "date-time": + a.Untyped = &schema.Untyped{} + default: + a.Untyped = &schema.Untyped{} + } + case proto.Boolean: + a.Scalar = ptr(schema.Boolean) + default: + a.Untyped = &schema.Untyped{} + } +} + +func (c *convert) VisitArbitrary(a *proto.Arbitrary) { + c.top().Untyped = &schema.Untyped{} +} + +func (c *convert) VisitReference(proto.Reference) { + // Do nothing, we handle references specially +} From 1751fc013f3e46486c25c5e625c0ef3cf9465438 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Tue, 29 Jan 2019 14:24:00 -0800 Subject: [PATCH 04/10] Update smd repo to handle CRDs --- .../structured-merge-diff/fieldpath/path.go | 6 + .../structured-merge-diff/fieldpath/set.go | 10 + .../structured-merge-diff/merge/update.go | 48 +++- .../structured-merge-diff/schema/BUILD | 2 + .../structured-merge-diff/schema/elements.go | 2 +- .../structured-merge-diff/schema/fromvalue.go | 57 ++++ .../structured-merge-diff/typed/BUILD | 2 + .../structured-merge-diff/typed/deduced.go | 178 ++++++++++++ .../structured-merge-diff/typed/helpers.go | 3 + .../structured-merge-diff/typed/parser.go | 61 ++++- .../structured-merge-diff/typed/remove.go | 119 ++++++++ .../structured-merge-diff/typed/typed.go | 255 ++++++++++-------- .../structured-merge-diff/typed/validate.go | 2 +- .../value/unstructured.go | 2 +- 14 files changed, 606 insertions(+), 141 deletions(-) create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/schema/fromvalue.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/deduced.go create mode 100644 vendor/sigs.k8s.io/structured-merge-diff/typed/remove.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go index 8c2225445d6..c11c7f28a5b 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go @@ -35,6 +35,12 @@ func (fp Path) String() string { return strings.Join(strs, "") } +func (fp Path) Copy() Path { + new := make(Path, len(fp)) + copy(new, fp) + return new +} + // MakePath constructs a Path. The parts may be PathElements, ints, strings. func MakePath(parts ...interface{}) (Path, error) { var fp Path diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go index 91e5dda1060..a4290c4d942 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go @@ -154,6 +154,16 @@ func (s *Set) iteratePrefix(prefix Path, f func(Path)) { s.Children.iteratePrefix(prefix, f) } +// WithPrefix returns the subset of paths which begin with the given prefix, +// with the prefix not included. +func (s *Set) WithPrefix(pe PathElement) *Set { + subset, ok := s.Children.Get(pe) + if !ok { + return NewSet() + } + return subset +} + // setNode is a pair of PathElement / Set, for the purpose of expressing // nested set membership. type setNode struct { diff --git a/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go b/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go index 3e7ce935728..5572258f238 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go @@ -85,6 +85,15 @@ func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpat for manager, conflictSet := range conflicts { managers[manager].Set = managers[manager].Set.Difference(conflictSet.Set) + if managers[manager].Set.Empty() { + delete(managers, manager) + } + } + + if _, ok := managers[workflow]; !ok { + managers[workflow] = &fieldpath.VersionedSet{ + Set: fieldpath.NewSet(), + } } return managers, nil @@ -105,13 +114,11 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa 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 + if managers[manager].Set.Empty() { + delete(managers, manager) + } return managers, nil } @@ -121,22 +128,41 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa 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) + return nil, 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 + return nil, fieldpath.ManagedFields{}, err + } + newObject, err = s.removeDisownedItems(newObject, configObject, managers[manager]) + if err != nil { + return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to remove fields: %v", 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) + return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err) } managers[manager] = &fieldpath.VersionedSet{ Set: set, APIVersion: version, } + if managers[manager].Set.Empty() { + delete(managers, manager) + } return newObject, managers, nil } + +func (s *Updater) removeDisownedItems(merged, applied typed.TypedValue, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) { + if lastSet.Set.Empty() { + return merged, nil + } + convertedApplied, err := s.Converter.Convert(applied, lastSet.APIVersion) + if err != nil { + return nil, fmt.Errorf("failed to convert applied config to last applied version: %v", err) + } + appliedSet, err := convertedApplied.ToFieldSet() + if err != nil { + return nil, fmt.Errorf("failed to create field set from applied config in last applied version: %v", err) + } + return merged.RemoveItems(lastSet.Set.Difference(appliedSet)), nil +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD index 65f9085e16b..2f81776994b 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD @@ -5,11 +5,13 @@ go_library( srcs = [ "doc.go", "elements.go", + "fromvalue.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"], + deps = ["//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library"], ) filegroup( diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go b/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go index a30bab883e8..d863bca7c42 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go @@ -207,7 +207,7 @@ func (s Schema) FindNamedType(name string) (TypeDef, bool) { // // 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) { +func (s *Schema) Resolve(tr TypeRef) (Atom, bool) { if tr.NamedType != nil { t, ok := s.FindNamedType(*tr.NamedType) if !ok { diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/fromvalue.go b/vendor/sigs.k8s.io/structured-merge-diff/schema/fromvalue.go new file mode 100644 index 00000000000..3c41d75796b --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/fromvalue.go @@ -0,0 +1,57 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package schema + +import ( + "sigs.k8s.io/structured-merge-diff/value" +) + +// TypeRefFromValue creates an inlined type from a value v +func TypeRefFromValue(v value.Value) TypeRef { + atom := atomFor(v) + return TypeRef{ + Inlined: atom, + } +} + +func atomFor(v value.Value) Atom { + switch { + // Untyped cases (handled at the bottom of this function) + case v.Null: + case v.ListValue != nil: + case v.FloatValue != nil: + case v.IntValue != nil: + case v.StringValue != nil: + case v.BooleanValue != nil: + // Recursive case + case v.MapValue != nil: + s := Struct{} + for i := range v.MapValue.Items { + child := v.MapValue.Items[i] + field := StructField{ + Name: child.Name, + Type: TypeRef{ + Inlined: atomFor(child.Value), + }, + } + s.Fields = append(s.Fields, field) + } + return Atom{Struct: &s} + } + + return Atom{Untyped: &Untyped{}} +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD index 1e6481fb1e4..7b2a669254c 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD @@ -3,10 +3,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ + "deduced.go", "doc.go", "helpers.go", "merge.go", "parser.go", + "remove.go", "typed.go", "validate.go", ], diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/deduced.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/deduced.go new file mode 100644 index 00000000000..0a8d3080a01 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/deduced.go @@ -0,0 +1,178 @@ +/* +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 ( + "reflect" + + "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/value" +) + +// deducedTypedValue holds a value and guesses what it is and what to +// do with it. +type deducedTypedValue struct { + value value.Value +} + +// AsTypedDeduced is going to generate it's own type definition based on +// the content of the object. This is useful for CRDs that don't have a +// validation field. +func AsTypedDeduced(v value.Value) TypedValue { + return deducedTypedValue{value: v} +} + +func (dv deducedTypedValue) AsValue() *value.Value { + return &dv.value +} + +func (deducedTypedValue) Validate() error { + return nil +} + +func (dv deducedTypedValue) ToFieldSet() (*fieldpath.Set, error) { + set := fieldpath.NewSet() + fieldsetDeduced(dv.value, fieldpath.Path{}, set) + return set, nil +} + +func fieldsetDeduced(v value.Value, path fieldpath.Path, set *fieldpath.Set) { + if v.MapValue == nil { + set.Insert(path) + return + } + + // We have a map. + // copy the existing path, append each item, and recursively call. + for i := range v.MapValue.Items { + child := v.MapValue.Items[i] + np := path.Copy() + np = append(np, fieldpath.PathElement{FieldName: &child.Name}) + fieldsetDeduced(child.Value, np, set) + } +} + +func (dv deducedTypedValue) Merge(pso TypedValue) (TypedValue, error) { + tpso, ok := pso.(deducedTypedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't merge deducedTypedValue with %T", tpso) + } + return AsTypedDeduced(mergeDeduced(dv.value, tpso.value)), nil +} + +func mergeDeduced(lhs, rhs value.Value) value.Value { + // If both sides are maps, merge them, otherwise return right + // side. + if rhs.MapValue == nil || lhs.MapValue == nil { + return rhs + } + + v := value.Value{MapValue: &value.Map{}} + for i := range lhs.MapValue.Items { + child := lhs.MapValue.Items[i] + v.MapValue.Set(child.Name, child.Value) + } + for i := range rhs.MapValue.Items { + child := rhs.MapValue.Items[i] + if sub, ok := v.MapValue.Get(child.Name); ok { + new := mergeDeduced(sub.Value, child.Value) + v.MapValue.Set(child.Name, new) + } else { + v.MapValue.Set(child.Name, child.Value) + } + } + return v +} + +func (dv deducedTypedValue) Compare(rhs TypedValue) (c *Comparison, err error) { + trhs, ok := rhs.(deducedTypedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't merge deducedTypedValue with %T", rhs) + } + + c = &Comparison{ + Removed: fieldpath.NewSet(), + Modified: fieldpath.NewSet(), + Added: fieldpath.NewSet(), + } + + added(dv.value, trhs.value, fieldpath.Path{}, c.Added) + added(trhs.value, dv.value, fieldpath.Path{}, c.Removed) + modified(dv.value, trhs.value, fieldpath.Path{}, c.Modified) + + merge, err := dv.Merge(rhs) + if err != nil { + return nil, err + } + c.Merged = merge + return c, nil +} + +func added(lhs, rhs value.Value, path fieldpath.Path, set *fieldpath.Set) { + if lhs.MapValue == nil && rhs.MapValue == nil { + // Both non-maps, nothing added, do nothing. + } else if lhs.MapValue == nil && rhs.MapValue != nil { + // From leaf to map, add leaf fields of map. + fieldsetDeduced(rhs, path, set) + } else if lhs.MapValue != nil && rhs.MapValue == nil { + // Went from map to field, add field. + set.Insert(path) + } else { + // Both are maps. + for i := range rhs.MapValue.Items { + child := rhs.MapValue.Items[i] + np := path.Copy() + np = append(np, fieldpath.PathElement{FieldName: &child.Name}) + + if v, ok := lhs.MapValue.Get(child.Name); ok { + added(v.Value, child.Value, np, set) + } else { + fieldsetDeduced(child.Value, np, set) + } + } + } +} + +func modified(lhs, rhs value.Value, path fieldpath.Path, set *fieldpath.Set) { + if lhs.MapValue == nil && rhs.MapValue == nil { + if !reflect.DeepEqual(lhs, rhs) { + set.Insert(path) + } + } else if lhs.MapValue != nil && rhs.MapValue != nil { + // Both are maps. + for i := range rhs.MapValue.Items { + child := rhs.MapValue.Items[i] + + v, ok := lhs.MapValue.Get(child.Name) + if !ok { + continue + } + + np := path.Copy() + np = append(np, fieldpath.PathElement{FieldName: &child.Name}) + modified(v.Value, child.Value, np, set) + } + } +} + +// RemoveItems does nothing because all lists in a deducedTypedValue are considered atomic, +// and there are no maps because it is indistinguishable from a struct. +func (dv deducedTypedValue) RemoveItems(_ *fieldpath.Set) TypedValue { + return dv +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go index 95b343014d0..e7fadc0182f 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go @@ -129,6 +129,9 @@ func (ef errorFormatter) validateScalar(t schema.Scalar, v *value.Value, prefix if v == nil { return nil } + if v.Null { + return nil + } switch t { case schema.Numeric: if v.FloatValue == nil && v.IntValue == 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 index de1585d6a60..bf21d2f910f 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go @@ -68,47 +68,80 @@ func (p *Parser) TypeNames() (names []string) { // 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{ +func (p *Parser) Type(name string) ParseableType { + return &parseableType{ parser: p, typename: name, } } // ParseableType allows for easy production of typed objects. -type ParseableType struct { +type ParseableType interface { + IsValid() bool + FromYAML(YAMLObject) (TypedValue, error) + FromUnstructured(interface{}) (TypedValue, error) +} + +type parseableType struct { parser *Parser typename string } +var _ ParseableType = &parseableType{} + // IsValid return true if p's schema and typename are valid. -func (p *ParseableType) IsValid() bool { +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) { +func (p *parseableType) FromYAML(object YAMLObject) (TypedValue, error) { v, err := value.FromYAML([]byte(object)) if err != nil { - return TypedValue{}, err + return nil, 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) { +func (p *parseableType) FromUnstructured(in interface{}) (TypedValue, error) { v, err := value.FromUnstructured(in) if err != nil { - return TypedValue{}, err + return nil, err } return AsTyped(v, &p.parser.Schema, p.typename) } + +// DeducedParseableType is a ParseableType that deduces the type from +// the content of the object. +type DeducedParseableType struct{} + +var _ ParseableType = DeducedParseableType{} + +// IsValid always returns true for a DeducedParseableType. +func (p DeducedParseableType) IsValid() bool { + return true +} + +// FromYAML parses a yaml string into an object and deduces the type for +// that object. +func (p DeducedParseableType) FromYAML(object YAMLObject) (TypedValue, error) { + v, err := value.FromYAML([]byte(object)) + if err != nil { + return nil, err + } + return AsTypedDeduced(v), nil +} + +// FromUnstructured converts a go interface to a TypedValue. It will return an +// error if the input object uses un-handled types. +func (p DeducedParseableType) FromUnstructured(in interface{}) (TypedValue, error) { + v, err := value.FromUnstructured(in) + if err != nil { + return nil, err + } + return AsTypedDeduced(v), nil +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/remove.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/remove.go new file mode 100644 index 00000000000..657fd1467f5 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/remove.go @@ -0,0 +1,119 @@ +/* +Copyright 2019 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package typed + +import ( + "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/schema" + "sigs.k8s.io/structured-merge-diff/value" +) + +type removingWalker struct { + value *value.Value + schema *schema.Schema + toRemove *fieldpath.Set +} + +func removeItemsWithSchema(value *value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef) { + w := &removingWalker{ + value: value, + schema: schema, + toRemove: toRemove, + } + resolveSchema(schema, typeRef, w) +} + +// doLeaf should be called on leaves before descending into children, if there +// will be a descent. It modifies w.inLeaf. +func (w *removingWalker) doLeaf() ValidationErrors { return nil } + +func (w *removingWalker) doScalar(t schema.Scalar) ValidationErrors { return nil } + +func (w *removingWalker) doStruct(t schema.Struct) ValidationErrors { + s := w.value.MapValue + + // If struct is null, empty, or atomic just return + if s == nil || len(s.Items) == 0 || t.ElementRelationship == schema.Atomic { + return nil + } + + fieldTypes := map[string]schema.TypeRef{} + for _, structField := range t.Fields { + fieldTypes[structField.Name] = structField.Type + } + + for i, _ := range s.Items { + item := s.Items[i] + pe := fieldpath.PathElement{FieldName: &item.Name} + if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { + removeItemsWithSchema(&s.Items[i].Value, subset, w.schema, fieldTypes[item.Name]) + } + } + return nil +} + +func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) { + l := w.value.ListValue + + // If list is null, empty, or atomic just return + if l == nil || len(l.Items) == 0 || t.ElementRelationship == schema.Atomic { + return nil + } + + newItems := []value.Value{} + for i, _ := range l.Items { + item := l.Items[i] + // Ignore error because we have already validated this list + pe, _ := listItemToPathElement(t, i, item) + path, _ := fieldpath.MakePath(pe) + if w.toRemove.Has(path) { + continue + } + if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { + removeItemsWithSchema(&l.Items[i], subset, w.schema, t.ElementType) + } + newItems = append(newItems, l.Items[i]) + } + l.Items = newItems + return nil +} + +func (w *removingWalker) doMap(t schema.Map) ValidationErrors { + m := w.value.MapValue + + // If map is null, empty, or atomic just return + if m == nil || len(m.Items) == 0 || t.ElementRelationship == schema.Atomic { + return nil + } + + newMap := &value.Map{} + for i, _ := range m.Items { + item := m.Items[i] + pe := fieldpath.PathElement{FieldName: &item.Name} + path, _ := fieldpath.MakePath(pe) + if w.toRemove.Has(path) { + continue + } + if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { + removeItemsWithSchema(&m.Items[i].Value, subset, w.schema, t.ElementType) + } + newMap.Set(item.Name, m.Items[i].Value) + } + w.value.MapValue = newMap + return nil +} + +func (*removingWalker) doUntyped(_ schema.Untyped) ValidationErrors { return nil } + +func (*removingWalker) errorf(_ string, _ ...interface{}) ValidationErrors { return nil } diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go index 3a7ee0ceb97..f3134b00d1d 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go @@ -25,44 +25,87 @@ import ( "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 +// TypedValue is a value with an associated type. +type TypedValue interface { + // AsValue removes the type from the TypedValue and only keeps the value. + AsValue() *value.Value + // Validate returns an error with a list of every spec violation. + Validate() error + // ToFieldSet creates a set containing every leaf field mentioned, or + // validation errors, if any were encountered. + ToFieldSet() (*fieldpath.Set, error) + // 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. + Merge(pso TypedValue) (TypedValue, error) + // 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. + Compare(rhs TypedValue) (c *Comparison, err error) + // RemoveItems removes each provided list or map item from the value. + RemoveItems(items *fieldpath.Set) TypedValue } // 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{ + tv := typedValue{ value: v, typeRef: schema.TypeRef{NamedType: &typeName}, schema: s, } if err := tv.Validate(); err != nil { - return TypedValue{}, err + return nil, err } return tv, nil } -// AsValue removes the type from the TypedValue and only keeps the value. -func (tv TypedValue) AsValue() *value.Value { +// AsTypeUnvalidated is just like AsTyped, but doesn't validate that the type +// conforms to the schema, for cases where that has already been checked or +// where you're going to call a method that validates as a side-effect (like +// ToFieldSet). +func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeName string) TypedValue { + tv := typedValue{ + value: v, + typeRef: schema.TypeRef{NamedType: &typeName}, + schema: s, + } + return tv +} + +// typedValue is a value of some specific type. +type typedValue struct { + value value.Value + typeRef schema.TypeRef + schema *schema.Schema +} + +var _ TypedValue = typedValue{} + +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 { +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) { +func (tv typedValue) ToFieldSet() (*fieldpath.Set, error) { s := fieldpath.NewSet() w := tv.walker() w.leafFieldCallback = func(p fieldpath.Path) { s.Insert(p) } @@ -72,19 +115,91 @@ func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) { 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) +func (tv typedValue) Merge(pso TypedValue) (TypedValue, error) { + tpso, ok := pso.(typedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't merge typedValue with %T", pso) + } + return merge(tv, tpso, ruleKeepRHS, nil) +} + +func (tv typedValue) Compare(rhs TypedValue) (c *Comparison, err error) { + trhs, ok := rhs.(typedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't compare typedValue with %T", rhs) + } + c = &Comparison{ + Removed: fieldpath.NewSet(), + Modified: fieldpath.NewSet(), + Added: fieldpath.NewSet(), + } + c.Merged, err = merge(tv, trhs, 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 +} + +// RemoveItems removes each provided list or map item from the value. +func (tv typedValue) RemoveItems(items *fieldpath.Set) TypedValue { + removeItemsWithSchema(&tv.value, items, tv.schema, tv.typeRef) + return tv +} + +func merge(lhs, rhs typedValue, rule, postRule mergeRule) (TypedValue, error) { + if lhs.schema != rhs.schema { + return nil, errorFormatter{}. + errorf("expected objects with types from the same schema") + } + if !reflect.DeepEqual(lhs.typeRef, rhs.typeRef) { + return nil, 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 nil, errs + } + + out := typedValue{ + schema: lhs.schema, + typeRef: lhs.typeRef, + } + if mw.out == nil { + out.value = value.Value{Null: true} + } else { + out.value = *mw.out + } + return out, nil } // Comparison is the return value of a TypedValue.Compare() operation. @@ -125,89 +240,3 @@ func (c *Comparison) String() string { } 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 index 38cb66c7174..9edf6fb64d1 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go @@ -22,7 +22,7 @@ import ( "sigs.k8s.io/structured-merge-diff/value" ) -func (tv TypedValue) walker() *validatingObjectWalker { +func (tv typedValue) walker() *validatingObjectWalker { return &validatingObjectWalker{ value: tv.value, schema: tv.schema, diff --git a/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go b/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go index 62660f2fca4..004bf224f46 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go @@ -31,7 +31,7 @@ import ( func FromYAML(input []byte) (Value, error) { var decoded interface{} - if len(input) == 0 || (len(input) == 4 && string(input) == "null") { + if len(input) == 4 && string(input) == "null" { // Special case since the yaml package doesn't accurately // preserve this. return Value{Null: true}, nil From b55417f429353e1109df8b3bfa2afc8dbd9f240b Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Tue, 29 Jan 2019 14:24:52 -0800 Subject: [PATCH 05/10] Plugin FieldManager in CRD handler, change to API --- pkg/kubectl/cmd/util/helpers.go | 4 +- staging/src/k8s.io/api/core/v1/types.go | 3 + .../pkg/apiserver/BUILD | 3 + .../pkg/apiserver/apiserver.go | 27 ++-- .../pkg/apiserver/customresource_handler.go | 53 +++++--- .../test/integration/BUILD | 2 + .../test/integration/apply_test.go | 117 ++++++++++++++++++ .../pkg/apis/meta/fuzzer/fuzzer.go | 6 + .../apimachinery/pkg/apis/meta/v1/BUILD | 1 + .../apimachinery/pkg/apis/meta/v1/meta.go | 14 +-- .../apimachinery/pkg/apis/meta/v1/types.go | 32 ++++- .../apis/meta/v1/unstructured/unstructured.go | 49 +++++--- .../apiserver/pkg/endpoints/handlers/BUILD | 1 + .../pkg/endpoints/handlers/create.go | 7 +- .../pkg/endpoints/handlers/fieldmanager/BUILD | 1 + .../handlers/fieldmanager/fieldmanager.go | 43 ++++++- .../handlers/fieldmanager/internal/BUILD | 2 +- .../fieldmanager/internal/gvkparser.go | 2 +- .../fieldmanager/internal/managedfields.go | 78 ++++++++++-- .../internal/managedfields_test.go | 83 +++++++++++-- .../fieldmanager/internal/typeconverter.go | 60 ++++++--- .../apiserver/pkg/endpoints/handlers/patch.go | 9 +- .../pkg/endpoints/handlers/update.go | 4 +- .../pkg/endpoints/patchhandler_test.go | 10 +- .../k8s.io/apiserver/pkg/registry/rest/BUILD | 2 + test/integration/apiserver/apply/BUILD | 1 + .../integration/apiserver/apply/apply_test.go | 98 +++++++++++++++ 27 files changed, 595 insertions(+), 117 deletions(-) create mode 100644 staging/src/k8s.io/apiextensions-apiserver/test/integration/apply_test.go diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index 303b5fea97b..0c6bf585a9c 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -400,8 +400,8 @@ func AddDryRunFlag(cmd *cobra.Command) { } func AddServerSideApplyFlags(cmd *cobra.Command) { - cmd.Flags().Bool("server-side", false, "If true, apply runs in the server instead of the client.") - cmd.Flags().Bool("force-conflicts", false, "If true, server-side apply will force the changes against conflicts.") + cmd.Flags().Bool("server-side", false, "If true, apply runs in the server instead of the client. This is an alpha feature and flag.") + cmd.Flags().Bool("force-conflicts", false, "If true, server-side apply will force the changes against conflicts. This is an alpha feature and flag.") } func AddIncludeUninitializedFlag(cmd *cobra.Command) { diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index 5a48afa740f..263d04c6ba3 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -3451,6 +3451,9 @@ type ServiceSpec struct { // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies // +patchMergeKey=port // +patchStrategy=merge + // +listType=map + // +listMapKey=port + // +listMapKey=protocol Ports []ServicePort `json:"ports,omitempty" patchStrategy:"merge" patchMergeKey:"port" protobuf:"bytes,1,rep,name=ports"` // Route service traffic to pods with label keys and values matching this diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD index 5da6c14cda3..1dbe1d792cc 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD @@ -55,11 +55,14 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/version:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go index b6830d762c8..09dc617ea14 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go @@ -21,6 +21,18 @@ import ( "net/http" "time" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + _ "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset" + _ "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions" + _ "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion" + internalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion" + "k8s.io/apiextensions-apiserver/pkg/controller/establish" + "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" + "k8s.io/apiextensions-apiserver/pkg/controller/status" + "k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -32,20 +44,6 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/apiserver/pkg/util/webhook" - - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset" - internalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion" - "k8s.io/apiextensions-apiserver/pkg/controller/establish" - "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" - "k8s.io/apiextensions-apiserver/pkg/controller/status" - "k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition" - - _ "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - _ "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions" - _ "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion" ) var ( @@ -184,6 +182,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) c.ExtraConfig.ServiceResolver, c.ExtraConfig.AuthResolverWrapper, c.ExtraConfig.MasterCount, + s.GenericAPIServer.Authorizer, ) if err != nil { return nil, err diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index 264a794b364..b26859e03e6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -28,8 +28,17 @@ import ( "github.com/go-openapi/spec" "github.com/go-openapi/strfmt" "github.com/go-openapi/validate" - "k8s.io/klog" - + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion" + apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + "k8s.io/apiextensions-apiserver/pkg/controller/establish" + "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" + "k8s.io/apiextensions-apiserver/pkg/crdserverscheme" + apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" + "k8s.io/apiextensions-apiserver/pkg/registry/customresource" + "k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor" apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -44,30 +53,22 @@ import ( "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/endpoints/handlers" + "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/metrics" apirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/storage/storagebackend" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/apiserver/pkg/util/webhook" "k8s.io/client-go/scale" "k8s.io/client-go/scale/scheme/autoscalingv1" "k8s.io/client-go/tools/cache" - - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion" - apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" - "k8s.io/apiextensions-apiserver/pkg/controller/establish" - "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" - "k8s.io/apiextensions-apiserver/pkg/crdserverscheme" - apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" - "k8s.io/apiextensions-apiserver/pkg/registry/customresource" - "k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor" - "k8s.io/apiserver/pkg/util/webhook" + "k8s.io/klog" ) // crdHandler serves the `/apis` endpoint. @@ -96,6 +97,9 @@ type crdHandler struct { masterCount int converterFactory *conversion.CRConverterFactory + + // so that we can do create on update. + authorizer authorizer.Authorizer } // crdInfo stores enough information to serve the storage for the custom resource @@ -134,7 +138,8 @@ func NewCustomResourceDefinitionHandler( establishingController *establish.EstablishingController, serviceResolver webhook.ServiceResolver, authResolverWrapper webhook.AuthenticationInfoResolverWrapper, - masterCount int) (*crdHandler, error) { + masterCount int, + authorizer authorizer.Authorizer) (*crdHandler, error) { ret := &crdHandler{ versionDiscoveryHandler: versionDiscoveryHandler, groupDiscoveryHandler: groupDiscoveryHandler, @@ -145,6 +150,7 @@ func NewCustomResourceDefinitionHandler( admission: admission, establishingController: establishingController, masterCount: masterCount, + authorizer: authorizer, } crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ UpdateFunc: ret.updateCustomResourceDefinition, @@ -228,6 +234,9 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { string(types.JSONPatchType), string(types.MergePatchType), } + if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) { + supportedTypes = append(supportedTypes, string(types.ApplyPatchType)) + } var handler http.HandlerFunc subresources, err := getSubresourcesForVersion(crd, requestInfo.APIVersion) @@ -565,6 +574,18 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource MetaGroupVersion: metav1.SchemeGroupVersion, TableConvertor: storages[v.Name].CustomResource, + + Authorizer: r.authorizer, + } + if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) { + reqScope := requestScopes[v.Name] + reqScope.FieldManager = fieldmanager.NewCRDFieldManager( + reqScope.Convertor, + reqScope.Defaulter, + reqScope.Kind.GroupVersion(), + reqScope.HubGroupVersion, + ) + requestScopes[v.Name] = reqScope } // override scaleSpec subresource values diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD b/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD index eb430540562..de11a45c451 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD @@ -9,6 +9,7 @@ load( go_test( name = "go_default_test", srcs = [ + "apply_test.go", "basic_test.go", "finalization_test.go", "objectmeta_test.go", @@ -41,6 +42,7 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library", "//staging/src/k8s.io/client-go/dynamic:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/apply_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/apply_test.go new file mode 100644 index 00000000000..595a7400750 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/apply_test.go @@ -0,0 +1,117 @@ +/* +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 integration + +import ( + "fmt" + "testing" + + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/test/integration/fixtures" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + genericfeatures "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" + "k8s.io/client-go/dynamic" +) + +func TestApplyBasic(t *testing.T) { + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + + tearDown, config, _, err := fixtures.StartDefaultServer(t) + if err != nil { + t.Fatal(err) + } + defer tearDown() + + apiExtensionClient, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + + noxuDefinition := fixtures.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped) + noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient) + if err != nil { + t.Fatal(err) + } + + kind := noxuDefinition.Spec.Names.Kind + apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version + + rest := apiExtensionClient.Discovery().RESTClient() + yamlBody := []byte(fmt.Sprintf(` +apiVersion: %s +kind: %s +metadata: + name: mytest +values: + numVal: 1 + boolVal: true + stringVal: "1"`, apiVersion, kind)) + result, err := rest.Patch(types.ApplyPatchType). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural). + Name("mytest"). + Body(yamlBody). + DoRaw() + if err != nil { + t.Fatal(err, string(result)) + } + + result, err = rest.Patch(types.MergePatchType). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural). + Name("mytest"). + Body([]byte(`{"values":{"numVal": 5}}`)). + DoRaw() + if err != nil { + t.Fatal(err, string(result)) + } + + // Re-apply the same object, we should get conflicts now. + result, err = rest.Patch(types.ApplyPatchType). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural). + Name("mytest"). + Body(yamlBody). + DoRaw() + if err == nil { + t.Fatalf("Expecting to get conflicts when applying object, got no error: %s", result) + } + status, ok := err.(*errors.StatusError) + if !ok { + t.Fatalf("Expecting to get conflicts as API error") + } + if len(status.Status().Details.Causes) < 1 { + t.Fatalf("Expecting to get at least one conflict when applying object, got: %v", status.Status().Details.Causes) + } + + // Re-apply with force, should work fine. + result, err = rest.Patch(types.ApplyPatchType). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural). + Name("mytest"). + Param("force", "true"). + Body(yamlBody). + DoRaw() + if err != nil { + t.Fatal(err, string(result)) + } + +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go index 3374caf2e45..b91b1e37fbf 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go @@ -273,6 +273,12 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { sort.Slice(j.MatchExpressions, func(a, b int) bool { return j.MatchExpressions[a].Key < j.MatchExpressions[b].Key }) } }, + func(j *metav1.ManagedFieldsEntry, c fuzz.Continue) { + c.FuzzNoCustom(j) + if j.Fields != nil && len(j.Fields.Map) == 0 { + j.Fields = nil + } + }, } } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD index e05c8d4eb0b..feb430e9789 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD @@ -16,6 +16,7 @@ go_test( "helpers_test.go", "labels_test.go", "micro_time_test.go", + "options_test.go", "time_test.go", "types_test.go", ], diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go index ea12b929cd8..7eecbd912dc 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go @@ -63,8 +63,8 @@ type Object interface { SetOwnerReferences([]OwnerReference) GetClusterName() string SetClusterName(clusterName string) - GetManagedFields() map[string]VersionedFields - SetManagedFields(lastApplied map[string]VersionedFields) + GetManagedFields() []ManagedFieldsEntry + SetManagedFields(lastApplied []ManagedFieldsEntry) } // ListMetaAccessor retrieves the list interface from an object @@ -171,13 +171,13 @@ func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName } func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName } -func (meta *ObjectMeta) GetManagedFields() map[string]VersionedFields { +func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields } -func (meta *ObjectMeta) SetManagedFields(ManagedFields map[string]VersionedFields) { - meta.ManagedFields = make(map[string]VersionedFields, len(ManagedFields)) - for key, value := range ManagedFields { - meta.ManagedFields[key] = value +func (meta *ObjectMeta) SetManagedFields(ManagedFields []ManagedFieldsEntry) { + meta.ManagedFields = make([]ManagedFieldsEntry, len(ManagedFields)) + for i, value := range ManagedFields { + meta.ManagedFields[i] = value } } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go index 44f77732480..30c07984e0a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go @@ -253,15 +253,18 @@ type ObjectMeta struct { // +optional ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"` - // ManagedFields is a map of workflow-id to the set of fields + // ManagedFields maps workflow-id and version to the set of fields // that are managed by that workflow. This is mostly for internal // housekeeping, and users typically shouldn't need to set or // understand this field. A workflow can be the user's name, a // controller's name, or the name of a specific apply path like // "ci-cd". The set of fields is always in the version that the // workflow used when modifying the object. + // + // This field is alpha and can be changed or removed without notice. + // // +optional - ManagedFields map[string]VersionedFields `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"` + ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"` } // Initializers tracks the progress of initialization. @@ -1043,18 +1046,35 @@ const ( LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist" ) -// VersionedFields is a pair of a FieldSet and the group version of the resource +// ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource // that the fieldset applies to. -type VersionedFields struct { +type ManagedFieldsEntry struct { + // Manager is an identifier of the workflow managing these fields. + Manager string `json:"manager,omitempty" protobuf:"bytes,1,opt,name=manager"` + // Operation is the type of operation which lead to this ManagedFieldsEntry being created. + // The only valid values for this field are 'Apply' and 'Update'. + Operation ManagedFieldsOperationType `json:"operation,omitempty" protobuf:"bytes,2,opt,name=operation,casttype=ManagedFieldsOperationType"` // APIVersion defines the version of this resource that this field set // applies to. The format is "group/version" just like the top-level // APIVersion field. It is necessary to track the version of a field // set because it cannot be automatically converted. - APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,1,opt,name=apiVersion"` + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,3,opt,name=apiVersion"` + // Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply' + // +optional + Time *Time `json:"time,omitempty" protobuf:"bytes,4,opt,name=time"` // Fields identifies a set of fields. - Fields Fields `json:"fields,omitempty" protobuf:"bytes,2,opt,name=fields,casttype=Fields"` + // +optional + Fields *Fields `json:"fields,omitempty" protobuf:"bytes,5,opt,name=fields,casttype=Fields"` } +// ManagedFieldsOperationType is the type of operation which lead to a ManagedFieldsEntry being created. +type ManagedFieldsOperationType string + +const ( + ManagedFieldsOperationApply ManagedFieldsOperationType = "Apply" + ManagedFieldsOperationUpdate ManagedFieldsOperationType = "Update" +) + // Fields stores a set of fields in a data structure like a Trie. // To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff type Fields struct { diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go index f24d65928af..1eaa85804f3 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go @@ -143,13 +143,20 @@ func (u *Unstructured) setNestedField(value interface{}, fields ...string) { SetNestedField(u.Object, value, fields...) } -func (u *Unstructured) setNestedSlice(value []string, fields ...string) { +func (u *Unstructured) setNestedStringSlice(value []string, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } SetNestedStringSlice(u.Object, value, fields...) } +func (u *Unstructured) setNestedSlice(value []interface{}, fields ...string) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + SetNestedSlice(u.Object, value, fields...) +} + func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) @@ -436,7 +443,7 @@ func (u *Unstructured) SetFinalizers(finalizers []string) { RemoveNestedField(u.Object, "metadata", "finalizers") return } - u.setNestedSlice(finalizers, "metadata", "finalizers") + u.setNestedStringSlice(finalizers, "metadata", "finalizers") } func (u *Unstructured) GetClusterName() string { @@ -451,27 +458,41 @@ func (u *Unstructured) SetClusterName(clusterName string) { u.setNestedField(clusterName, "metadata", "clusterName") } -func (u *Unstructured) GetManagedFields() map[string]metav1.VersionedFields { - m, found, err := nestedMapNoCopy(u.Object, "metadata", "managedFields") +func (u *Unstructured) GetManagedFields() []metav1.ManagedFieldsEntry { + items, found, err := NestedSlice(u.Object, "metadata", "managedFields") if !found || err != nil { return nil } - out := &map[string]metav1.VersionedFields{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, out); err != nil { - utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object: %v", err)) - return nil + managedFields := []metav1.ManagedFieldsEntry{} + for _, item := range items { + m, ok := item.(map[string]interface{}) + if !ok { + utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object, item %v is not a map", item)) + return nil + } + out := metav1.ManagedFieldsEntry{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, &out); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object: %v", err)) + return nil + } + managedFields = append(managedFields, out) } - return *out + return managedFields } -func (u *Unstructured) SetManagedFields(managedFields map[string]metav1.VersionedFields) { +func (u *Unstructured) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) { if managedFields == nil { RemoveNestedField(u.Object, "metadata", "managedFields") return } - out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&managedFields) - if err != nil { - utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object: %v", err)) + items := []interface{}{} + for _, managedFieldsEntry := range managedFields { + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&managedFieldsEntry) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to set managedFields for object: %v", err)) + return + } + items = append(items, out) } - u.setNestedField(out, "metadata", "managedFields") + u.setNestedSlice(items, "metadata", "managedFields") } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD index b774f6c73ce..cb7d20abf3c 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD @@ -88,6 +88,7 @@ go_library( "//vendor/github.com/evanphx/json-patch:go_default_library", "//vendor/golang.org/x/net/websocket:go_default_library", "//vendor/k8s.io/klog:go_default_library", + "//vendor/k8s.io/utils/trace:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go index 9a2f80e378e..f8c2dd4c347 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/http" + "strings" "time" "k8s.io/apimachinery/pkg/api/errors" @@ -140,7 +141,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte return } - obj, err = scope.FieldManager.Update(liveObj, obj, "create") + obj, err = scope.FieldManager.Update(liveObj, obj, prefixFromUserAgent(req.UserAgent())) if err != nil { scope.err(fmt.Errorf("failed to update object managed fields: %v", err), w, req) return @@ -191,3 +192,7 @@ type namedCreaterAdapter struct { func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { return c.Creater.Create(ctx, obj, createValidatingAdmission, options) } + +func prefixFromUserAgent(u string) string { + return strings.Split(u, "/")[0] +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD index 9ae6f2ce54d..ce474dbbc45 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD @@ -8,6 +8,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go index 9e21d03acf1..f4c1cdd3e2b 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go @@ -18,8 +18,10 @@ package fieldmanager import ( "fmt" + "time" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" @@ -60,6 +62,22 @@ func NewFieldManager(models openapiproto.Models, objectConverter runtime.ObjectC }, nil } +// NewCRDFieldManager creates a new FieldManager specifically for +// CRDs. This doesn't use openapi models (and it doesn't support the +// validation field right now). +func NewCRDFieldManager(objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) *FieldManager { + return &FieldManager{ + typeConverter: internal.DeducedTypeConverter{}, + objectConverter: objectConverter, + objectDefaulter: objectDefaulter, + groupVersion: gv, + hubVersion: hub, + updater: merge.Updater{ + Converter: internal.NewVersionConverter(internal.DeducedTypeConverter{}, objectConverter, hub), + }, + } +} + // Update is used when the object has already been merged (non-apply // use-case), and simply updates the managed fields in the output // object. @@ -97,6 +115,11 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r return nil, fmt.Errorf("failed to create typed live object: %v", err) } apiVersion := fieldpath.APIVersion(f.groupVersion.String()) + manager, err = f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate) + if err != nil { + return nil, fmt.Errorf("failed to build manager identifier: %v", err) + } + managed, err = f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed, manager) if err != nil { return nil, fmt.Errorf("failed to update ManagedFields: %v", err) @@ -134,8 +157,13 @@ func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) ( if err != nil { return nil, fmt.Errorf("failed to create typed live object: %v", err) } + manager, err := f.buildManagerInfo(applyManager, metav1.ManagedFieldsOperationApply) + if err != nil { + return nil, fmt.Errorf("failed to build manager identifier: %v", err) + } + apiVersion := fieldpath.APIVersion(f.groupVersion.String()) - newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, applyManager, force) + newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, manager, force) if err != nil { if conflicts, ok := err.(merge.Conflicts); ok { return nil, errors.NewApplyConflict(conflicts) @@ -172,3 +200,16 @@ func (f *FieldManager) toVersioned(obj runtime.Object) (runtime.Object, error) { func (f *FieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) { return f.objectConverter.ConvertToVersion(obj, f.hubVersion) } + +func (f *FieldManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) { + managerInfo := metav1.ManagedFieldsEntry{ + Manager: prefix, + Operation: operation, + APIVersion: f.groupVersion.String(), + Time: &metav1.Time{Time: time.Now().UTC()}, + } + if managerInfo.Manager == "" { + managerInfo.Manager = "unknown" + } + return internal.DecodeManager(&managerInfo) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD index d8924d72890..8f0fe68bcb4 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/BUILD @@ -38,7 +38,7 @@ go_test( "typeconverter_test.go", "versionconverter_test.go", ], - data = ["//api/openapi-spec:swagger-spec"], + data = ["//api/openapi-spec"], embed = [":go_default_library"], deps = [ "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go index ce9a0492445..14d90f964f1 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go @@ -35,7 +35,7 @@ type gvkParser struct { parser typed.Parser } -func (p *gvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType { +func (p *gvkParser) Type(gvk schema.GroupVersionKind) typed.ParseableType { typeName, ok := p.gvks[gvk] if !ok { return nil diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go index ffe7485e986..b2f32646233 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go @@ -17,7 +17,9 @@ limitations under the License. package internal import ( + "encoding/json" "fmt" + "sort" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -72,21 +74,53 @@ func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedField // decodeManagedFields converts ManagedFields from the wire format (api format) // to the format used by sigs.k8s.io/structured-merge-diff -func decodeManagedFields(encodedManagedFields map[string]metav1.VersionedFields) (managedFields fieldpath.ManagedFields, err error) { +func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) { managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields)) - for manager, encodedVersionedSet := range encodedManagedFields { + for _, encodedVersionedSet := range encodedManagedFields { + manager, err := DecodeManager(&encodedVersionedSet) + if err != nil { + return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err) + } managedFields[manager], err = decodeVersionedSet(&encodedVersionedSet) if err != nil { - return nil, fmt.Errorf("error decoding versioned set for %v: %v", manager, err) + return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err) } } return managedFields, nil } -func decodeVersionedSet(encodedVersionedSet *metav1.VersionedFields) (versionedSet *fieldpath.VersionedSet, err error) { +// DecodeManager creates a manager identifier string from a ManagedFieldsEntry +func DecodeManager(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) { + encodedManagerCopy := *encodedManager + + // Never include the fields in the manager identifier + encodedManagerCopy.Fields = nil + + // For appliers, don't include the APIVersion or Time in the manager identifier, + // so it will always have the same manager identifier each time it applied. + if encodedManager.Operation == metav1.ManagedFieldsOperationApply { + encodedManagerCopy.APIVersion = "" + encodedManagerCopy.Time = nil + } + + // Use the remaining fields to build the manager identifier + b, err := json.Marshal(&encodedManagerCopy) + if err != nil { + return "", fmt.Errorf("error marshalling manager identifier: %v", err) + } + + return string(b), nil +} + +func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet *fieldpath.VersionedSet, err error) { versionedSet = &fieldpath.VersionedSet{} versionedSet.APIVersion = fieldpath.APIVersion(encodedVersionedSet.APIVersion) - set, err := FieldsToSet(encodedVersionedSet.Fields) + + fields := metav1.Fields{} + if encodedVersionedSet.Fields != nil { + fields = *encodedVersionedSet.Fields + } + set, err := FieldsToSet(fields) if err != nil { return nil, fmt.Errorf("error decoding set: %v", err) } @@ -96,24 +130,42 @@ func decodeVersionedSet(encodedVersionedSet *metav1.VersionedFields) (versionedS // encodeManagedFields converts ManagedFields from the the format used by // sigs.k8s.io/structured-merge-diff to the the wire format (api format) -func encodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields map[string]metav1.VersionedFields, err error) { - encodedManagedFields = make(map[string]metav1.VersionedFields, len(managedFields)) - for manager, versionedSet := range managedFields { - v, err := encodeVersionedSet(versionedSet) +func encodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) { + // Sort the keys so a predictable order will be used. + managers := []string{} + for manager := range managedFields { + managers = append(managers, manager) + } + sort.Strings(managers) + + encodedManagedFields = []metav1.ManagedFieldsEntry{} + for _, manager := range managers { + versionedSet := managedFields[manager] + v, err := encodeManagerVersionedSet(manager, versionedSet) if err != nil { return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err) } - encodedManagedFields[manager] = *v + encodedManagedFields = append(encodedManagedFields, *v) } return encodedManagedFields, nil } -func encodeVersionedSet(versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.VersionedFields, err error) { - encodedVersionedSet = &metav1.VersionedFields{} +func encodeManagerVersionedSet(manager string, versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) { + encodedVersionedSet = &metav1.ManagedFieldsEntry{} + + // Get as many fields as we can from the manager identifier + err = json.Unmarshal([]byte(manager), encodedVersionedSet) + if err != nil { + return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", manager, err) + } + + // Get the APIVersion and Fields from the VersionedSet encodedVersionedSet.APIVersion = string(versionedSet.APIVersion) - encodedVersionedSet.Fields, err = SetToFields(*versionedSet.Set) + fields, err := SetToFields(*versionedSet.Set) if err != nil { return nil, fmt.Errorf("error encoding set: %v", err) } + encodedVersionedSet.Fields = &fields + return encodedVersionedSet, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go index dca8b93c73c..7f2d517f574 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go @@ -29,29 +29,36 @@ import ( // sigs.k8s.io/structured-merge-diff to the wire format (api format) and back func TestRoundTripManagedFields(t *testing.T) { tests := []string{ - `foo: - apiVersion: v1 + `- apiVersion: v1 fields: - i:5: - f:i: {} v:3: f:alsoPi: {} v:3.1415: f:pi: {} v:false: f:notTrue: {} + manager: foo + operation: Update + time: "2001-02-03T04:05:06Z" +- apiVersion: v1beta1 + fields: + i:5: + f:i: {} + manager: foo + operation: Update + time: "2011-12-13T14:15:16Z" `, - `foo: - apiVersion: v1 + `- apiVersion: v1 fields: f:spec: f:containers: k:{"name":"c"}: f:image: {} f:name: {} + manager: foo + operation: Apply `, - `foo: - apiVersion: v1 + `- apiVersion: v1 fields: f:apiVersion: {} f:kind: {} @@ -77,9 +84,10 @@ func TestRoundTripManagedFields(t *testing.T) { f:ports: i:0: f:containerPort: {} + manager: foo + operation: Update `, - `foo: - apiVersion: v1 + `- apiVersion: v1 fields: f:allowVolumeExpansion: {} f:apiVersion: {} @@ -92,9 +100,10 @@ func TestRoundTripManagedFields(t *testing.T) { f:secretName: {} f:secretNamespace: {} f:provisioner: {} + manager: foo + operation: Apply `, - `foo: - apiVersion: v1 + `- apiVersion: v1 fields: f:apiVersion: {} f:kind: {} @@ -114,12 +123,14 @@ func TestRoundTripManagedFields(t *testing.T) { f:name: {} f:served: {} f:storage: {} + manager: foo + operation: Update `, } for _, test := range tests { t.Run(test, func(t *testing.T) { - var unmarshaled map[string]metav1.VersionedFields + var unmarshaled []metav1.ManagedFieldsEntry if err := yaml.Unmarshal([]byte(test), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } @@ -141,3 +152,49 @@ func TestRoundTripManagedFields(t *testing.T) { }) } } + +func TestDecodeManager(t *testing.T) { + tests := []struct { + managedFieldsEntry string + expected string + }{ + { + managedFieldsEntry: ` +apiVersion: v1 +fields: + f:apiVersion: {} +manager: foo +operation: Update +time: "2001-02-03T04:05:06Z" +`, + expected: "{\"manager\":\"foo\",\"operation\":\"Update\",\"apiVersion\":\"v1\",\"time\":\"2001-02-03T04:05:06Z\"}", + }, + { + managedFieldsEntry: ` +apiVersion: v1 +fields: + f:apiVersion: {} +manager: foo +operation: Apply +time: "2001-02-03T04:05:06Z" +`, + expected: "{\"manager\":\"foo\",\"operation\":\"Apply\"}", + }, + } + + for _, test := range tests { + t.Run(test.managedFieldsEntry, func(t *testing.T) { + var unmarshaled metav1.ManagedFieldsEntry + if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + decoded, err := DecodeManager(&unmarshaled) + if err != nil { + t.Fatalf("did not expect decoding error but got: %v", err) + } + if !reflect.DeepEqual(decoded, test.expected) { + t.Fatalf("expected:\n%v\nbut got:\n%v", test.expected, decoded) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go index 79f3017cb43..b4974752dea 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go @@ -21,21 +21,52 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kube-openapi/pkg/util/proto" "sigs.k8s.io/structured-merge-diff/typed" + "sigs.k8s.io/structured-merge-diff/value" "sigs.k8s.io/yaml" ) // TypeConverter allows you to convert from runtime.Object to // typed.TypedValue and the other way around. type TypeConverter interface { - NewTyped(schema.GroupVersionKind) (typed.TypedValue, error) ObjectToTyped(runtime.Object) (typed.TypedValue, error) YAMLToTyped([]byte) (typed.TypedValue, error) TypedToObject(typed.TypedValue) (runtime.Object, error) } +// DeducedTypeConverter is a TypeConverter for CRDs that don't have a +// schema. It does implement the same interface though (and create the +// same types of objects), so that everything can still work the same. +// CRDs are merged with all their fields being "atomic" (lists +// included). +// +// Note that this is not going to be sufficient for converting to/from +// CRDs that have a schema defined (we don't support that schema yet). +type DeducedTypeConverter struct{} + +var _ TypeConverter = DeducedTypeConverter{} + +// ObjectToTyped converts an object into a TypedValue with a "deduced type". +func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) { + u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, err + } + return typed.DeducedParseableType{}.FromUnstructured(u) +} + +// YAMLToTyped parses a yaml object into a TypedValue with a "deduced type". +func (DeducedTypeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) { + return typed.DeducedParseableType{}.FromYAML(typed.YAMLObject(from)) +} + +// TypedToObject transforms the typed value into a runtime.Object. That +// is not specific to deduced type. +func (DeducedTypeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) { + return valueToObject(value.AsValue()) +} + type typeConverter struct { parser *gvkParser } @@ -53,28 +84,15 @@ func NewTypeConverter(models proto.Models) (TypeConverter, error) { return &typeConverter{parser: parser}, nil } -func (c *typeConverter) NewTyped(gvk schema.GroupVersionKind) (typed.TypedValue, error) { - t := c.parser.Type(gvk) - if t == nil { - return typed.TypedValue{}, fmt.Errorf("no corresponding type for %v", gvk) - } - - u, err := t.New() - if err != nil { - return typed.TypedValue{}, fmt.Errorf("new typed: %v", err) - } - return u, nil -} - func (c *typeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) { u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) if err != nil { - return typed.TypedValue{}, err + return nil, err } gvk := obj.GetObjectKind().GroupVersionKind() t := c.parser.Type(gvk) if t == nil { - return typed.TypedValue{}, fmt.Errorf("no corresponding type for %v", gvk) + return nil, fmt.Errorf("no corresponding type for %v", gvk) } return t.FromUnstructured(u) } @@ -83,14 +101,18 @@ func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) { unstructured := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(from, &unstructured.Object); err != nil { - return typed.TypedValue{}, fmt.Errorf("error decoding YAML: %v", err) + return nil, fmt.Errorf("error decoding YAML: %v", err) } return c.ObjectToTyped(unstructured) } func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) { - vu := value.AsValue().ToUnstructured(false) + return valueToObject(value.AsValue()) +} + +func valueToObject(value *value.Value) (runtime.Object, error) { + vu := value.ToUnstructured(false) u, ok := vu.(map[string]interface{}) if !ok { return nil, fmt.Errorf("failed to convert typed to unstructured: want map, got %T", vu) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go index b674dcdd4b1..2d0d5f08075 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go @@ -201,6 +201,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface name: name, patchType: patchType, patchBytes: patchBytes, + userAgent: req.UserAgent(), trace: trace, } @@ -268,6 +269,7 @@ type patcher struct { name string patchType types.PatchType patchBytes []byte + userAgent string trace *utiltrace.Trace @@ -309,7 +311,7 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r } if p.fieldManager != nil { - if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, "jsonPatcher"); err != nil { + if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, prefixFromUserAgent(p.userAgent)); err != nil { return nil, fmt.Errorf("failed to update object managed fields: %v", err) } } @@ -371,7 +373,7 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru } if p.fieldManager != nil { - if newObj, err = p.fieldManager.Update(currentObject, newObj, "smPatcher"); err != nil { + if newObj, err = p.fieldManager.Update(currentObject, newObj, prefixFromUserAgent(p.userAgent)); err != nil { return nil, fmt.Errorf("failed to update object managed fields: %v", err) } } @@ -395,6 +397,9 @@ func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Ob if p.options.Force != nil { force = *p.options.Force } + if p.fieldManager == nil { + panic("FieldManager must be installed to run apply") + } return p.fieldManager.Apply(obj, p.patch, force) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go index e78346c5b0a..a57e913d5bb 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go @@ -123,8 +123,8 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac userInfo, _ := request.UserFrom(ctx) transformers := []rest.TransformFunc{} if scope.FieldManager != nil { - transformers = append(transformers, func(_ context.Context, liveObj, newObj runtime.Object) (runtime.Object, error) { - if obj, err = scope.FieldManager.Update(liveObj, newObj, "update"); err != nil { + transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) { + if obj, err = scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent())); err != nil { return nil, fmt.Errorf("failed to update object managed fields: %v", err) } return obj, nil diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go index a630fd4e058..f96c80c8bd4 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go @@ -191,7 +191,7 @@ func TestPatchApply(t *testing.T) { if simpleStorage.updated.Other != "bar" { t.Errorf(`Merge should have kept initial "bar" value for Other: %v`, simpleStorage.updated.Other) } - if _, ok := simpleStorage.updated.ObjectMeta.ManagedFields["default"]; !ok { + if len(simpleStorage.updated.ObjectMeta.ManagedFields) == 0 { t.Errorf(`Expected managedFields field to be set, but is empty`) } } @@ -230,11 +230,11 @@ func TestApplyAddsGVK(t *testing.T) { } // TODO: Need to fix this expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}` - if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected { + if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected { t.Errorf( `Expected managedFields field to be %q, got %q`, expected, - simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion, + simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion, ) } } @@ -265,11 +265,11 @@ func TestApplyCreatesWithManagedFields(t *testing.T) { } // TODO: Need to fix this expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}` - if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected { + if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected { t.Errorf( `Expected managedFields field to be %q, got %q`, expected, - simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion, + simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion, ) } } diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/BUILD b/staging/src/k8s.io/apiserver/pkg/registry/rest/BUILD index 37d58875c5f..7ad6f3b6fa8 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/BUILD @@ -48,7 +48,9 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/test/integration/apiserver/apply/BUILD b/test/integration/apiserver/apply/BUILD index d5633a713d1..a8bb1c1f599 100644 --- a/test/integration/apiserver/apply/BUILD +++ b/test/integration/apiserver/apply/BUILD @@ -9,6 +9,7 @@ go_test( deps = [ "//pkg/master:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apiserver/pkg/features:go_default_library", diff --git a/test/integration/apiserver/apply/apply_test.go b/test/integration/apiserver/apply/apply_test.go index 4bbbaf59804..f66f6a171a8 100644 --- a/test/integration/apiserver/apply/apply_test.go +++ b/test/integration/apiserver/apply/apply_test.go @@ -17,10 +17,13 @@ limitations under the License. package apiserver import ( + "encoding/json" "net/http/httptest" "testing" + "time" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" genericfeatures "k8s.io/apiserver/pkg/features" @@ -204,3 +207,98 @@ func TestApplyUpdateApplyConflictForced(t *testing.T) { t.Fatalf("Failed to apply object with force: %v", err) } } + +// TestApplyManagedFields makes sure that managedFields api does not change +func TestApplyManagedFields(t *testing.T) { + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() + + _, client, closeFn := setup(t) + defer closeFn() + + _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). + Namespace("default"). + Resource("configmaps"). + Name("test-cm"). + Body([]byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "test-cm" + }, + "data": { + "key": "value" + } + }`)). + Do(). + Get() + if err != nil { + t.Fatalf("Failed to create object using Apply patch: %v", err) + } + + _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). + Namespace("default"). + Resource("configmaps"). + Name("test-cm"). + Body([]byte(`{"data":{"key": "new value"}}`)).Do().Get() + if err != nil { + t.Fatalf("Failed to patch object: %v", err) + } + + object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do().Get() + if err != nil { + t.Fatalf("Failed to retrieve object: %v", err) + } + + accessor, err := meta.Accessor(object) + if err != nil { + t.Fatalf("Failed to get meta accessor: %v", err) + } + + actual, err := json.MarshalIndent(object, "\t", "\t") + if err != nil { + t.Fatalf("Failed to marshal object: %v", err) + } + + expected := []byte(`{ + "metadata": { + "name": "test-cm", + "namespace": "default", + "selfLink": "` + accessor.GetSelfLink() + `", + "uid": "` + string(accessor.GetUID()) + `", + "resourceVersion": "` + accessor.GetResourceVersion() + `", + "creationTimestamp": "` + accessor.GetCreationTimestamp().UTC().Format(time.RFC3339) + `", + "managedFields": [ + { + "manager": "apply", + "operation": "Apply", + "apiVersion": "v1", + "fields": { + "f:apiVersion": {}, + "f:kind": {}, + "f:metadata": { + "f:name": {} + } + } + }, + { + "manager": "` + accessor.GetManagedFields()[1].Manager + `", + "operation": "Update", + "apiVersion": "v1", + "time": "` + accessor.GetManagedFields()[1].Time.UTC().Format(time.RFC3339) + `", + "fields": { + "f:data": { + "f:key": {} + } + } + } + ] + }, + "data": { + "key": "new value" + } + }`) + + if string(expected) != string(actual) { + t.Fatalf("Expected:\n%v\nGot:\n%v", string(expected), string(actual)) + } +} From 5949154ec55e13be6877fb4aa17b89652b82c6f8 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Tue, 29 Jan 2019 14:26:39 -0800 Subject: [PATCH 06/10] Run generation scripts --- Godeps/Godeps.json | 26 +- Godeps/LICENSES | 1255 +++++++++++++++++ api/openapi-spec/swagger.json | 875 ++++++++++++ .../src/k8s.io/api/core/v1/generated.proto | 3 + .../Godeps/Godeps.json | 46 +- .../k8s.io/apimachinery/Godeps/Godeps.json | 22 +- .../pkg/apis/meta/v1/generated.pb.go | 1197 +++++++++++++--- .../pkg/apis/meta/v1/generated.proto | 64 + .../meta/v1/types_swagger_doc_generated.go | 34 +- .../pkg/apis/meta/v1/zz_generated.deepcopy.go | 90 ++ .../src/k8s.io/apiserver/Godeps/Godeps.json | 30 +- .../src/k8s.io/cli-runtime/Godeps/Godeps.json | 22 +- .../src/k8s.io/client-go/Godeps/Godeps.json | 22 +- .../k8s.io/cloud-provider/Godeps/Godeps.json | 22 +- staging/src/k8s.io/csi-api/Godeps/Godeps.json | 22 +- .../k8s.io/kube-aggregator/Godeps/Godeps.json | 34 +- staging/src/k8s.io/metrics/Godeps/Godeps.json | 22 +- .../src/k8s.io/node-api/Godeps/Godeps.json | 22 +- .../sample-apiserver/Godeps/Godeps.json | 34 +- .../sample-cli-plugin/Godeps/Godeps.json | 22 +- .../sample-controller/Godeps/Godeps.json | 22 +- 21 files changed, 3670 insertions(+), 216 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 32cf504a3cc..be3e2606a44 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -2095,7 +2095,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/google/uuid", @@ -4083,6 +4083,10 @@ "ImportPath": "k8s.io/kube-openapi/pkg/handler", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/schemaconv", + "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" + }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" @@ -4159,6 +4163,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Comment": "v1.1.0", diff --git a/Godeps/LICENSES b/Godeps/LICENSES index 88d6c73df87..ff751ab2d40 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -114523,6 +114523,216 @@ third-party archives. ================================================================================ +================================================================================ += vendor/k8s.io/kube-openapi/pkg/schemaconv licensed under: = + + + 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. + += vendor/k8s.io/kube-openapi/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + ================================================================================ = vendor/k8s.io/kube-openapi/pkg/util licensed under: = @@ -118512,6 +118722,1051 @@ third-party archives. ================================================================================ +================================================================================ += vendor/sigs.k8s.io/structured-merge-diff/fieldpath licensed under: = + + 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. + += vendor/sigs.k8s.io/structured-merge-diff/LICENSE e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + +================================================================================ += vendor/sigs.k8s.io/structured-merge-diff/merge licensed under: = + + 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. + += vendor/sigs.k8s.io/structured-merge-diff/LICENSE e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + +================================================================================ += vendor/sigs.k8s.io/structured-merge-diff/schema licensed under: = + + 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. + += vendor/sigs.k8s.io/structured-merge-diff/LICENSE e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + +================================================================================ += vendor/sigs.k8s.io/structured-merge-diff/typed licensed under: = + + 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. + += vendor/sigs.k8s.io/structured-merge-diff/LICENSE e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + +================================================================================ += vendor/sigs.k8s.io/structured-merge-diff/value licensed under: = + + 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. + += vendor/sigs.k8s.io/structured-merge-diff/LICENSE e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + ================================================================================ = vendor/sigs.k8s.io/yaml licensed under: = diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index af9b361ad24..402aadc6425 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -1335,6 +1335,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -1860,6 +1867,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -2385,6 +2399,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -2910,6 +2931,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -3435,6 +3463,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -3609,6 +3644,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -4134,6 +4176,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -5468,6 +5517,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -5993,6 +6049,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -6518,6 +6581,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -6692,6 +6762,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -6866,6 +6943,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -7391,6 +7475,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -7565,6 +7656,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -8090,6 +8188,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -8615,6 +8720,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -9054,6 +9166,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -9752,6 +9871,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -10020,6 +10146,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -10263,6 +10396,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -10772,6 +10912,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -11446,6 +11593,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -12052,6 +12206,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -12218,6 +12379,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -17891,6 +18059,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -18400,6 +18575,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -19379,6 +19561,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -19545,6 +19734,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -20322,6 +20518,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -20488,6 +20691,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -21232,6 +21442,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -21398,6 +21615,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -22474,6 +22698,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -22999,6 +23230,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -23173,6 +23411,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -23698,6 +23943,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -23872,6 +24124,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -24046,6 +24305,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -24571,6 +24837,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -24745,6 +25018,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -24919,6 +25199,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -25444,6 +25731,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -25618,6 +25912,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -25792,6 +26093,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -28313,6 +28621,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -28838,6 +29153,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -29103,6 +29425,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -29277,6 +29606,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -29802,6 +30138,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -29976,6 +30319,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -30150,6 +30500,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -32041,6 +32398,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -32566,6 +32930,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -32740,6 +33111,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -33265,6 +33643,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -33439,6 +33824,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -33613,6 +34005,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -34138,6 +34537,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -34312,6 +34718,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -34486,6 +34899,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -35011,6 +35431,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -35185,6 +35612,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -35359,6 +35793,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -37711,6 +38152,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -39557,6 +40005,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -39731,6 +40186,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -40701,6 +41163,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -40875,6 +41344,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -41845,6 +42321,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -42019,6 +42502,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -43022,6 +43512,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -43196,6 +43693,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -44166,6 +44670,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -44340,6 +44851,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -45310,6 +45828,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -45484,6 +46009,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -46382,6 +46914,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -46625,6 +47164,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -47507,6 +48053,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -48477,6 +49030,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -49480,6 +50040,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -50677,6 +51244,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -50851,6 +51425,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -51376,6 +51957,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -51641,6 +52229,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -51815,6 +52410,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -52340,6 +52942,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -52514,6 +53123,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -53039,6 +53655,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -53564,6 +54187,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -53738,6 +54368,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -53912,6 +54549,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -54086,6 +54730,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -54700,6 +55351,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -57157,6 +57815,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -58160,6 +58825,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -58334,6 +59006,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -58948,6 +59627,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -60024,6 +60710,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -60517,6 +61210,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -61018,6 +61718,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -61527,6 +62234,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -63289,6 +64003,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -63782,6 +64503,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -64283,6 +65011,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -64792,6 +65527,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -66554,6 +67296,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -67047,6 +67796,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -67548,6 +68304,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -68057,6 +68820,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -69868,6 +70638,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -70612,6 +71389,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -71397,6 +72181,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -72392,6 +73183,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -72901,6 +73699,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -73067,6 +73872,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -74013,6 +74825,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -74757,6 +75576,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -75266,6 +76092,13 @@ "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "name": "dryRun", "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "name": "force", + "in": "query" } ], "responses": { @@ -86425,6 +87258,11 @@ "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ServicePort" }, + "x-kubernetes-list-map-keys": [ + "port", + "protocol" + ], + "x-kubernetes-list-type": "map", "x-kubernetes-patch-merge-key": "port", "x-kubernetes-patch-strategy": "merge" }, @@ -92305,6 +93143,10 @@ } ] }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Fields": { + "description": "Fields stores a set of fields in a data structure like a Trie. To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff", + "type": "object" + }, "io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery": { "description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", "type": "object", @@ -92423,6 +93265,32 @@ } } }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" + }, + "fields": { + "description": "Fields identifies a set of fields.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Fields" + }, + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" + }, + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" + }, + "time": { + "description": "Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply'", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + } + }, "io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime": { "description": "MicroTime is version of Time with microsecond level precision.", "type": "string", @@ -92484,6 +93352,13 @@ "type": "string" } }, + "managedFields": { + "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.\n\nThis field is alpha and can be changed or removed without notice.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" + } + }, "name": { "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names", "type": "string" diff --git a/staging/src/k8s.io/api/core/v1/generated.proto b/staging/src/k8s.io/api/core/v1/generated.proto index b13a2db72f5..9fb5666cd9b 100644 --- a/staging/src/k8s.io/api/core/v1/generated.proto +++ b/staging/src/k8s.io/api/core/v1/generated.proto @@ -4248,6 +4248,9 @@ message ServiceSpec { // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies // +patchMergeKey=port // +patchStrategy=merge + // +listType=map + // +listMapKey=port + // +listMapKey=protocol repeated ServicePort ports = 1; // Route service traffic to pods with label keys and values matching this diff --git a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json index 57ac02a4fa3..0b3f8a7018d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json @@ -484,7 +484,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -1478,6 +1478,14 @@ "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/negotiation", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2254,6 +2262,10 @@ "ImportPath": "k8s.io/kube-openapi/pkg/handler", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/schemaconv", + "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" + }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" @@ -2402,6 +2414,10 @@ "ImportPath": "k8s.io/apiserver/pkg/admission", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/authorization/authorizer", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/endpoints/discovery", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2410,6 +2426,10 @@ "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2422,6 +2442,10 @@ "ImportPath": "k8s.io/apiserver/pkg/endpoints/request", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/features", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/registry/generic", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2566,6 +2590,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/apimachinery/Godeps/Godeps.json b/staging/src/k8s.io/apimachinery/Godeps/Godeps.json index ce90141c15b..11556f9de26 100644 --- a/staging/src/k8s.io/apimachinery/Godeps/Godeps.json +++ b/staging/src/k8s.io/apimachinery/Godeps/Godeps.json @@ -60,7 +60,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -178,6 +178,26 @@ "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go index 4fa6f315738..455688962cb 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go @@ -33,6 +33,7 @@ limitations under the License. DeleteOptions Duration ExportOptions + Fields GetOptions GroupKind GroupResource @@ -47,10 +48,12 @@ limitations under the License. List ListMeta ListOptions + ManagedFieldsEntry MicroTime ObjectMeta OwnerReference Patch + PatchOptions Preconditions RootPaths ServerAddressByClientCIDR @@ -130,131 +133,143 @@ func (m *ExportOptions) Reset() { *m = ExportOptions{} } func (*ExportOptions) ProtoMessage() {} func (*ExportOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (m *Fields) Reset() { *m = Fields{} } +func (*Fields) ProtoMessage() {} +func (*Fields) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } + func (m *GetOptions) Reset() { *m = GetOptions{} } func (*GetOptions) ProtoMessage() {} -func (*GetOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*GetOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } func (m *GroupKind) Reset() { *m = GroupKind{} } func (*GroupKind) ProtoMessage() {} -func (*GroupKind) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } +func (*GroupKind) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } func (m *GroupResource) Reset() { *m = GroupResource{} } func (*GroupResource) ProtoMessage() {} -func (*GroupResource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } +func (*GroupResource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } func (m *GroupVersion) Reset() { *m = GroupVersion{} } func (*GroupVersion) ProtoMessage() {} -func (*GroupVersion) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } +func (*GroupVersion) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } func (m *GroupVersionForDiscovery) Reset() { *m = GroupVersionForDiscovery{} } func (*GroupVersionForDiscovery) ProtoMessage() {} func (*GroupVersionForDiscovery) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{13} + return fileDescriptorGenerated, []int{14} } func (m *GroupVersionKind) Reset() { *m = GroupVersionKind{} } func (*GroupVersionKind) ProtoMessage() {} -func (*GroupVersionKind) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } +func (*GroupVersionKind) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } func (m *GroupVersionResource) Reset() { *m = GroupVersionResource{} } func (*GroupVersionResource) ProtoMessage() {} -func (*GroupVersionResource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } +func (*GroupVersionResource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } func (m *Initializer) Reset() { *m = Initializer{} } func (*Initializer) ProtoMessage() {} -func (*Initializer) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } +func (*Initializer) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } func (m *Initializers) Reset() { *m = Initializers{} } func (*Initializers) ProtoMessage() {} -func (*Initializers) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } +func (*Initializers) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } func (m *LabelSelector) Reset() { *m = LabelSelector{} } func (*LabelSelector) ProtoMessage() {} -func (*LabelSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } +func (*LabelSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } func (m *LabelSelectorRequirement) Reset() { *m = LabelSelectorRequirement{} } func (*LabelSelectorRequirement) ProtoMessage() {} func (*LabelSelectorRequirement) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{19} + return fileDescriptorGenerated, []int{20} } func (m *List) Reset() { *m = List{} } func (*List) ProtoMessage() {} -func (*List) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} } +func (*List) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } func (m *ListMeta) Reset() { *m = ListMeta{} } func (*ListMeta) ProtoMessage() {} -func (*ListMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } +func (*ListMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } func (m *ListOptions) Reset() { *m = ListOptions{} } func (*ListOptions) ProtoMessage() {} -func (*ListOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } +func (*ListOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } + +func (m *ManagedFieldsEntry) Reset() { *m = ManagedFieldsEntry{} } +func (*ManagedFieldsEntry) ProtoMessage() {} +func (*ManagedFieldsEntry) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } func (m *MicroTime) Reset() { *m = MicroTime{} } func (*MicroTime) ProtoMessage() {} -func (*MicroTime) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } +func (*MicroTime) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } func (m *ObjectMeta) Reset() { *m = ObjectMeta{} } func (*ObjectMeta) ProtoMessage() {} -func (*ObjectMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } +func (*ObjectMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } func (m *OwnerReference) Reset() { *m = OwnerReference{} } func (*OwnerReference) ProtoMessage() {} -func (*OwnerReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } +func (*OwnerReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } func (m *Patch) Reset() { *m = Patch{} } func (*Patch) ProtoMessage() {} -func (*Patch) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } +func (*Patch) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } + +func (m *PatchOptions) Reset() { *m = PatchOptions{} } +func (*PatchOptions) ProtoMessage() {} +func (*PatchOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } func (m *Preconditions) Reset() { *m = Preconditions{} } func (*Preconditions) ProtoMessage() {} -func (*Preconditions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } +func (*Preconditions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } func (m *RootPaths) Reset() { *m = RootPaths{} } func (*RootPaths) ProtoMessage() {} -func (*RootPaths) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } +func (*RootPaths) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } func (m *ServerAddressByClientCIDR) Reset() { *m = ServerAddressByClientCIDR{} } func (*ServerAddressByClientCIDR) ProtoMessage() {} func (*ServerAddressByClientCIDR) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{29} + return fileDescriptorGenerated, []int{32} } func (m *Status) Reset() { *m = Status{} } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } func (m *StatusCause) Reset() { *m = StatusCause{} } func (*StatusCause) ProtoMessage() {} -func (*StatusCause) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } +func (*StatusCause) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{34} } func (m *StatusDetails) Reset() { *m = StatusDetails{} } func (*StatusDetails) ProtoMessage() {} -func (*StatusDetails) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } +func (*StatusDetails) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{35} } func (m *Time) Reset() { *m = Time{} } func (*Time) ProtoMessage() {} -func (*Time) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } +func (*Time) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{36} } func (m *Timestamp) Reset() { *m = Timestamp{} } func (*Timestamp) ProtoMessage() {} -func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{34} } +func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{37} } func (m *TypeMeta) Reset() { *m = TypeMeta{} } func (*TypeMeta) ProtoMessage() {} -func (*TypeMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{35} } +func (*TypeMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{38} } func (m *UpdateOptions) Reset() { *m = UpdateOptions{} } func (*UpdateOptions) ProtoMessage() {} -func (*UpdateOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{36} } +func (*UpdateOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{39} } func (m *Verbs) Reset() { *m = Verbs{} } func (*Verbs) ProtoMessage() {} -func (*Verbs) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{37} } +func (*Verbs) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{40} } func (m *WatchEvent) Reset() { *m = WatchEvent{} } func (*WatchEvent) ProtoMessage() {} -func (*WatchEvent) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{38} } +func (*WatchEvent) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{41} } func init() { proto.RegisterType((*APIGroup)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.APIGroup") @@ -266,6 +281,7 @@ func init() { proto.RegisterType((*DeleteOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.DeleteOptions") proto.RegisterType((*Duration)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Duration") proto.RegisterType((*ExportOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ExportOptions") + proto.RegisterType((*Fields)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Fields") proto.RegisterType((*GetOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GetOptions") proto.RegisterType((*GroupKind)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind") proto.RegisterType((*GroupResource)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupResource") @@ -280,10 +296,12 @@ func init() { proto.RegisterType((*List)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.List") proto.RegisterType((*ListMeta)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta") proto.RegisterType((*ListOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ListOptions") + proto.RegisterType((*ManagedFieldsEntry)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry") proto.RegisterType((*MicroTime)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime") proto.RegisterType((*ObjectMeta)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta") proto.RegisterType((*OwnerReference)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.OwnerReference") proto.RegisterType((*Patch)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Patch") + proto.RegisterType((*PatchOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.PatchOptions") proto.RegisterType((*Preconditions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Preconditions") proto.RegisterType((*RootPaths)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.RootPaths") proto.RegisterType((*ServerAddressByClientCIDR)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR") @@ -702,6 +720,55 @@ func (m *ExportOptions) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *Fields) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Fields) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Map) > 0 { + keysForMap := make([]string, 0, len(m.Map)) + for k := range m.Map { + keysForMap = append(keysForMap, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForMap) + for _, k := range keysForMap { + dAtA[i] = 0xa + i++ + v := m.Map[string(k)] + msgSize := 0 + if (&v) != nil { + msgSize = (&v).Size() + msgSize += 1 + sovGenerated(uint64(msgSize)) + } + mapSize := 1 + len(k) + sovGenerated(uint64(len(k))) + msgSize + i = encodeVarintGenerated(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) + n4, err := (&v).MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + } + return i, nil +} + func (m *GetOptions) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -941,11 +1008,11 @@ func (m *Initializers) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Result.Size())) - n4, err := m.Result.MarshalTo(dAtA[i:]) + n5, err := m.Result.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n4 + i += n5 } return i, nil } @@ -1061,11 +1128,11 @@ func (m *List) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n5, err := m.ListMeta.MarshalTo(dAtA[i:]) + n6, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n5 + i += n6 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -1161,6 +1228,56 @@ func (m *ListOptions) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *ManagedFieldsEntry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ManagedFieldsEntry) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Manager))) + i += copy(dAtA[i:], m.Manager) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operation))) + i += copy(dAtA[i:], m.Operation) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) + i += copy(dAtA[i:], m.APIVersion) + if m.Time != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Time.Size())) + n7, err := m.Time.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.Fields != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Fields.Size())) + n8, err := m.Fields.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} + func (m *ObjectMeta) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1206,20 +1323,20 @@ func (m *ObjectMeta) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x42 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.CreationTimestamp.Size())) - n6, err := m.CreationTimestamp.MarshalTo(dAtA[i:]) + n9, err := m.CreationTimestamp.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n9 if m.DeletionTimestamp != nil { dAtA[i] = 0x4a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.DeletionTimestamp.Size())) - n7, err := m.DeletionTimestamp.MarshalTo(dAtA[i:]) + n10, err := m.DeletionTimestamp.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n10 } if m.DeletionGracePeriodSeconds != nil { dAtA[i] = 0x50 @@ -1307,11 +1424,25 @@ func (m *ObjectMeta) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Initializers.Size())) - n8, err := m.Initializers.MarshalTo(dAtA[i:]) + n11, err := m.Initializers.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n11 + } + if len(m.ManagedFields) > 0 { + for _, msg := range m.ManagedFields { + dAtA[i] = 0x8a + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } } return i, nil } @@ -1388,6 +1519,49 @@ func (m *Patch) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *PatchOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PatchOptions) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.DryRun) > 0 { + for _, s := range m.DryRun { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if m.Force != nil { + dAtA[i] = 0x10 + i++ + if *m.Force { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + return i, nil +} + func (m *Preconditions) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1489,11 +1663,11 @@ func (m *Status) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n9, err := m.ListMeta.MarshalTo(dAtA[i:]) + n12, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n9 + i += n12 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) @@ -1510,11 +1684,11 @@ func (m *Status) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Details.Size())) - n10, err := m.Details.MarshalTo(dAtA[i:]) + n13, err := m.Details.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n10 + i += n13 } dAtA[i] = 0x30 i++ @@ -1739,11 +1913,11 @@ func (m *WatchEvent) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Object.Size())) - n11, err := m.Object.MarshalTo(dAtA[i:]) + n14, err := m.Object.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n11 + i += n14 return i, nil } @@ -1910,6 +2084,21 @@ func (m *ExportOptions) Size() (n int) { return n } +func (m *Fields) Size() (n int) { + var l int + _ = l + if len(m.Map) > 0 { + for k, v := range m.Map { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + return n +} + func (m *GetOptions) Size() (n int) { var l int _ = l @@ -2087,6 +2276,26 @@ func (m *ListOptions) Size() (n int) { return n } +func (m *ManagedFieldsEntry) Size() (n int) { + var l int + _ = l + l = len(m.Manager) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operation) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.APIVersion) + n += 1 + l + sovGenerated(uint64(l)) + if m.Time != nil { + l = m.Time.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Fields != nil { + l = m.Fields.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *ObjectMeta) Size() (n int) { var l int _ = l @@ -2146,6 +2355,12 @@ func (m *ObjectMeta) Size() (n int) { l = m.Initializers.Size() n += 2 + l + sovGenerated(uint64(l)) } + if len(m.ManagedFields) > 0 { + for _, e := range m.ManagedFields { + l = e.Size() + n += 2 + l + sovGenerated(uint64(l)) + } + } return n } @@ -2175,6 +2390,21 @@ func (m *Patch) Size() (n int) { return n } +func (m *PatchOptions) Size() (n int) { + var l int + _ = l + if len(m.DryRun) > 0 { + for _, s := range m.DryRun { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.Force != nil { + n += 2 + } + return n +} + func (m *Preconditions) Size() (n int) { var l int _ = l @@ -2422,6 +2652,26 @@ func (this *ExportOptions) String() string { }, "") return s } +func (this *Fields) String() string { + if this == nil { + return "nil" + } + keysForMap := make([]string, 0, len(this.Map)) + for k := range this.Map { + keysForMap = append(keysForMap, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForMap) + mapStringForMap := "map[string]Fields{" + for _, k := range keysForMap { + mapStringForMap += fmt.Sprintf("%v: %v,", k, this.Map[k]) + } + mapStringForMap += "}" + s := strings.Join([]string{`&Fields{`, + `Map:` + mapStringForMap + `,`, + `}`, + }, "") + return s +} func (this *GetOptions) String() string { if this == nil { return "nil" @@ -2536,6 +2786,20 @@ func (this *ListOptions) String() string { }, "") return s } +func (this *ManagedFieldsEntry) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ManagedFieldsEntry{`, + `Manager:` + fmt.Sprintf("%v", this.Manager) + `,`, + `Operation:` + fmt.Sprintf("%v", this.Operation) + `,`, + `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, + `Time:` + strings.Replace(fmt.Sprintf("%v", this.Time), "Time", "Time", 1) + `,`, + `Fields:` + strings.Replace(fmt.Sprintf("%v", this.Fields), "Fields", "Fields", 1) + `,`, + `}`, + }, "") + return s +} func (this *ObjectMeta) String() string { if this == nil { return "nil" @@ -2577,6 +2841,7 @@ func (this *ObjectMeta) String() string { `Finalizers:` + fmt.Sprintf("%v", this.Finalizers) + `,`, `ClusterName:` + fmt.Sprintf("%v", this.ClusterName) + `,`, `Initializers:` + strings.Replace(fmt.Sprintf("%v", this.Initializers), "Initializers", "Initializers", 1) + `,`, + `ManagedFields:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ManagedFields), "ManagedFieldsEntry", "ManagedFieldsEntry", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -2605,6 +2870,17 @@ func (this *Patch) String() string { }, "") return s } +func (this *PatchOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PatchOptions{`, + `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, + `Force:` + valueToStringGenerated(this.Force) + `,`, + `}`, + }, "") + return s +} func (this *Preconditions) String() string { if this == nil { return "nil" @@ -3957,6 +4233,179 @@ func (m *ExportOptions) Unmarshal(dAtA []byte) error { } return nil } +func (m *Fields) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Fields: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Fields: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Map", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Map == nil { + m.Map = make(map[string]Fields) + } + var mapkey string + mapvalue := &Fields{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &Fields{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Map[mapkey] = *mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *GetOptions) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -5744,6 +6193,209 @@ func (m *ListOptions) Unmarshal(dAtA []byte) error { } return nil } +func (m *ManagedFieldsEntry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ManagedFieldsEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ManagedFieldsEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Manager", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Manager = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Operation = ManagedFieldsOperationType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.APIVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Time == nil { + m.Time = &Time{} + } + if err := m.Time.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Fields == nil { + m.Fields = &Fields{} + } + if err := m.Fields.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ObjectMeta) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -6407,6 +7059,37 @@ func (m *ObjectMeta) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ManagedFields", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ManagedFields = append(m.ManagedFields, ManagedFieldsEntry{}) + if err := m.ManagedFields[len(m.ManagedFields)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6686,6 +7369,106 @@ func (m *Patch) Unmarshal(dAtA []byte) error { } return nil } +func (m *PatchOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PatchOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PatchOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.Force = &b + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Preconditions) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -8098,159 +8881,171 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 2453 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x59, 0x4d, 0x6c, 0x23, 0x49, - 0xf5, 0x4f, 0xdb, 0xb1, 0x63, 0x3f, 0xc7, 0xf9, 0xa8, 0x9d, 0xfd, 0xff, 0xbd, 0x91, 0x88, 0xb3, - 0xbd, 0x68, 0x95, 0x85, 0x59, 0x9b, 0x64, 0x61, 0x35, 0x0c, 0x30, 0x90, 0x8e, 0x33, 0x43, 0xd8, - 0xc9, 0xc4, 0xaa, 0xec, 0x0c, 0x62, 0x18, 0x21, 0x3a, 0xdd, 0x15, 0xa7, 0x49, 0xbb, 0xdb, 0x5b, - 0xd5, 0xce, 0x8c, 0xe1, 0xc0, 0x1e, 0x40, 0x80, 0x84, 0xd0, 0x1c, 0x39, 0xa1, 0x1d, 0xc1, 0x85, - 0x2b, 0x27, 0x4e, 0x9c, 0x90, 0x98, 0xe3, 0x4a, 0x5c, 0xf6, 0x80, 0xac, 0x9d, 0x70, 0xe0, 0x84, - 0xb8, 0xe7, 0x80, 0x50, 0x55, 0x57, 0x77, 0x57, 0xdb, 0xf1, 0xa4, 0xcd, 0x2c, 0x88, 0x53, 0xdc, - 0xef, 0xe3, 0xf7, 0x5e, 0x55, 0xbd, 0xfa, 0xd5, 0xab, 0x0a, 0xec, 0x9d, 0x5c, 0x63, 0x0d, 0xc7, - 0x6f, 0x9e, 0xf4, 0x0f, 0x09, 0xf5, 0x48, 0x40, 0x58, 0xf3, 0x94, 0x78, 0xb6, 0x4f, 0x9b, 0x52, - 0x61, 0xf6, 0x9c, 0xae, 0x69, 0x1d, 0x3b, 0x1e, 0xa1, 0x83, 0x66, 0xef, 0xa4, 0xc3, 0x05, 0xac, - 0xd9, 0x25, 0x81, 0xd9, 0x3c, 0xdd, 0x68, 0x76, 0x88, 0x47, 0xa8, 0x19, 0x10, 0xbb, 0xd1, 0xa3, - 0x7e, 0xe0, 0xa3, 0x4f, 0x87, 0x5e, 0x0d, 0xd5, 0xab, 0xd1, 0x3b, 0xe9, 0x70, 0x01, 0x6b, 0x70, - 0xaf, 0xc6, 0xe9, 0xc6, 0xca, 0x9b, 0x1d, 0x27, 0x38, 0xee, 0x1f, 0x36, 0x2c, 0xbf, 0xdb, 0xec, - 0xf8, 0x1d, 0xbf, 0x29, 0x9c, 0x0f, 0xfb, 0x47, 0xe2, 0x4b, 0x7c, 0x88, 0x5f, 0x21, 0xe8, 0xca, - 0xc4, 0x54, 0x68, 0xdf, 0x0b, 0x9c, 0x2e, 0x19, 0xcd, 0x62, 0xe5, 0xed, 0xcb, 0x1c, 0x98, 0x75, - 0x4c, 0xba, 0xe6, 0xa8, 0x9f, 0xfe, 0xa7, 0x3c, 0x94, 0xb6, 0xda, 0xbb, 0xb7, 0xa8, 0xdf, 0xef, - 0xa1, 0x35, 0x98, 0xf5, 0xcc, 0x2e, 0xa9, 0x69, 0x6b, 0xda, 0x7a, 0xd9, 0x98, 0x7f, 0x3a, 0xac, - 0xcf, 0x9c, 0x0d, 0xeb, 0xb3, 0x77, 0xcc, 0x2e, 0xc1, 0x42, 0x83, 0x5c, 0x28, 0x9d, 0x12, 0xca, - 0x1c, 0xdf, 0x63, 0xb5, 0xdc, 0x5a, 0x7e, 0xbd, 0xb2, 0x79, 0xa3, 0x91, 0x65, 0xfc, 0x0d, 0x11, - 0xe0, 0x5e, 0xe8, 0x7a, 0xd3, 0xa7, 0x2d, 0x87, 0x59, 0xfe, 0x29, 0xa1, 0x03, 0x63, 0x49, 0x46, - 0x29, 0x49, 0x25, 0xc3, 0x71, 0x04, 0xf4, 0x23, 0x0d, 0x96, 0x7a, 0x94, 0x1c, 0x11, 0x4a, 0x89, - 0x2d, 0xf5, 0xb5, 0xfc, 0x9a, 0xf6, 0x09, 0x84, 0xad, 0xc9, 0xb0, 0x4b, 0xed, 0x11, 0x7c, 0x3c, - 0x16, 0x11, 0xfd, 0x5a, 0x83, 0x15, 0x46, 0xe8, 0x29, 0xa1, 0x5b, 0xb6, 0x4d, 0x09, 0x63, 0xc6, - 0x60, 0xdb, 0x75, 0x88, 0x17, 0x6c, 0xef, 0xb6, 0x30, 0xab, 0xcd, 0x8a, 0x79, 0xf8, 0x6a, 0xb6, - 0x84, 0x0e, 0x26, 0xe1, 0x18, 0xba, 0xcc, 0x68, 0x65, 0xa2, 0x09, 0xc3, 0xcf, 0x49, 0x43, 0x3f, - 0x82, 0xf9, 0x68, 0x21, 0x6f, 0x3b, 0x2c, 0x40, 0xf7, 0xa0, 0xd8, 0xe1, 0x1f, 0xac, 0xa6, 0x89, - 0x04, 0x1b, 0xd9, 0x12, 0x8c, 0x30, 0x8c, 0x05, 0x99, 0x4f, 0x51, 0x7c, 0x32, 0x2c, 0xd1, 0xf4, - 0x9f, 0xcd, 0x42, 0x65, 0xab, 0xbd, 0x8b, 0x09, 0xf3, 0xfb, 0xd4, 0x22, 0x19, 0x8a, 0x66, 0x13, - 0x80, 0xff, 0x65, 0x3d, 0xd3, 0x22, 0x76, 0x2d, 0xb7, 0xa6, 0xad, 0x97, 0x0c, 0x24, 0xed, 0xe0, - 0x4e, 0xac, 0xc1, 0x8a, 0x15, 0x47, 0x3d, 0x71, 0x3c, 0x5b, 0xac, 0xb6, 0x82, 0xfa, 0x8e, 0xe3, - 0xd9, 0x58, 0x68, 0xd0, 0x6d, 0x28, 0x9c, 0x12, 0x7a, 0xc8, 0xe7, 0x9f, 0x17, 0xc4, 0x67, 0xb3, - 0x0d, 0xef, 0x1e, 0x77, 0x31, 0xca, 0x67, 0xc3, 0x7a, 0x41, 0xfc, 0xc4, 0x21, 0x08, 0x6a, 0x00, - 0xb0, 0x63, 0x9f, 0x06, 0x22, 0x9d, 0x5a, 0x61, 0x2d, 0xbf, 0x5e, 0x36, 0x16, 0x78, 0x7e, 0x07, - 0xb1, 0x14, 0x2b, 0x16, 0xe8, 0x1a, 0xcc, 0x33, 0xc7, 0xeb, 0xf4, 0x5d, 0x93, 0x72, 0x41, 0xad, - 0x28, 0xf2, 0xbc, 0x22, 0xf3, 0x9c, 0x3f, 0x50, 0x74, 0x38, 0x65, 0xc9, 0x23, 0x59, 0x66, 0x40, - 0x3a, 0x3e, 0x75, 0x08, 0xab, 0xcd, 0x25, 0x91, 0xb6, 0x63, 0x29, 0x56, 0x2c, 0xd0, 0x6b, 0x50, - 0x10, 0x33, 0x5f, 0x2b, 0x89, 0x10, 0x55, 0x19, 0xa2, 0x20, 0x96, 0x05, 0x87, 0x3a, 0xf4, 0x06, - 0xcc, 0xc9, 0x5d, 0x53, 0x2b, 0x0b, 0xb3, 0x45, 0x69, 0x36, 0x17, 0x95, 0x75, 0xa4, 0x47, 0xdf, - 0x00, 0xc4, 0x02, 0x9f, 0x9a, 0x1d, 0x22, 0x55, 0x5f, 0x37, 0xd9, 0x71, 0x0d, 0x84, 0xd7, 0x8a, - 0xf4, 0x42, 0x07, 0x63, 0x16, 0xf8, 0x02, 0x2f, 0xfd, 0x77, 0x1a, 0x2c, 0x2a, 0xb5, 0x20, 0xea, - 0xee, 0x1a, 0xcc, 0x77, 0x94, 0x5d, 0x27, 0xeb, 0x22, 0x9e, 0x19, 0x75, 0x47, 0xe2, 0x94, 0x25, - 0x22, 0x50, 0xa6, 0x12, 0x29, 0x62, 0x97, 0x8d, 0xcc, 0x45, 0x1b, 0xe5, 0x90, 0x44, 0x52, 0x84, - 0x0c, 0x27, 0xc8, 0xfa, 0xdf, 0x34, 0x51, 0xc0, 0x11, 0xdf, 0xa0, 0x75, 0x85, 0xd3, 0x34, 0xb1, - 0x1c, 0xf3, 0x13, 0xf8, 0xe8, 0x12, 0x22, 0xc8, 0xfd, 0x4f, 0x10, 0xc1, 0xf5, 0xd2, 0x2f, 0x3f, - 0xa8, 0xcf, 0xbc, 0xff, 0x97, 0xb5, 0x19, 0xfd, 0x2d, 0xa8, 0x6e, 0x53, 0x62, 0x06, 0x64, 0xbf, - 0x17, 0x88, 0x01, 0xe8, 0x50, 0xb4, 0xe9, 0x00, 0xf7, 0x3d, 0x39, 0x50, 0xe0, 0xfb, 0xbb, 0x25, - 0x24, 0x58, 0x6a, 0xf4, 0x9f, 0xe4, 0xa1, 0xda, 0x22, 0x2e, 0x49, 0xbc, 0x6e, 0x02, 0xea, 0x50, - 0xd3, 0x22, 0x6d, 0x42, 0x1d, 0xdf, 0x3e, 0x20, 0x96, 0xef, 0xd9, 0x4c, 0xac, 0x6b, 0xde, 0xf8, - 0x3f, 0x5e, 0x2d, 0xb7, 0xc6, 0xb4, 0xf8, 0x02, 0x0f, 0xe4, 0x42, 0xb5, 0x47, 0xc5, 0x6f, 0x27, - 0x90, 0x27, 0x08, 0xdf, 0xb9, 0x6f, 0x65, 0x9b, 0xb0, 0xb6, 0xea, 0x6a, 0x2c, 0x9f, 0x0d, 0xeb, - 0xd5, 0x94, 0x08, 0xa7, 0xc1, 0xd1, 0xd7, 0x60, 0xc9, 0xa7, 0xbd, 0x63, 0xd3, 0x6b, 0x91, 0x1e, - 0xf1, 0x6c, 0xe2, 0x05, 0x4c, 0xb0, 0x49, 0xc9, 0xb8, 0xc2, 0x79, 0x7f, 0x7f, 0x44, 0x87, 0xc7, - 0xac, 0xd1, 0x7d, 0x58, 0xee, 0x51, 0xbf, 0x67, 0x76, 0x4c, 0x8e, 0xd8, 0xf6, 0x5d, 0xc7, 0x1a, - 0x08, 0xb6, 0x29, 0x1b, 0x57, 0xcf, 0x86, 0xf5, 0xe5, 0xf6, 0xa8, 0xf2, 0x7c, 0x58, 0x7f, 0x49, - 0x4c, 0x1d, 0x97, 0x24, 0x4a, 0x3c, 0x0e, 0xa3, 0xac, 0x44, 0x61, 0xe2, 0x4a, 0xec, 0x42, 0xa9, - 0xd5, 0xa7, 0xc2, 0x0b, 0x7d, 0x05, 0x4a, 0xb6, 0xfc, 0x2d, 0x67, 0xfe, 0xd5, 0xe8, 0xe0, 0x8c, - 0x6c, 0xce, 0x87, 0xf5, 0x2a, 0x3f, 0xea, 0x1b, 0x91, 0x00, 0xc7, 0x2e, 0xfa, 0x03, 0xa8, 0xee, - 0x3c, 0xea, 0xf9, 0x34, 0x88, 0xd6, 0xf4, 0x75, 0x28, 0x12, 0x21, 0x10, 0x68, 0xa5, 0x84, 0xed, - 0x43, 0x33, 0x2c, 0xb5, 0x9c, 0x7d, 0xc8, 0x23, 0xd3, 0x0a, 0x24, 0x6d, 0xc7, 0xec, 0xb3, 0xc3, - 0x85, 0x38, 0xd4, 0xe9, 0xfb, 0x00, 0xb7, 0x48, 0x0c, 0xbd, 0x05, 0x8b, 0xd1, 0x66, 0x4b, 0x73, - 0xc0, 0xff, 0x4b, 0xe7, 0x45, 0x9c, 0x56, 0xe3, 0x51, 0x7b, 0xfd, 0x01, 0x94, 0x05, 0x4f, 0x70, - 0xba, 0x4f, 0x08, 0x50, 0x7b, 0x0e, 0x01, 0x46, 0xe7, 0x45, 0x6e, 0xd2, 0x79, 0xa1, 0x6c, 0x0b, - 0x17, 0xaa, 0xa1, 0x6f, 0x74, 0x84, 0x65, 0x8a, 0x70, 0x15, 0x4a, 0x51, 0x9a, 0x32, 0x4a, 0xdc, - 0xba, 0x44, 0x40, 0x38, 0xb6, 0x50, 0xa2, 0x1d, 0x43, 0x8a, 0xf3, 0xb2, 0x05, 0x53, 0xf8, 0x3c, - 0xf7, 0x7c, 0x3e, 0x57, 0x22, 0xfd, 0x10, 0x6a, 0x93, 0xfa, 0x9d, 0x17, 0x60, 0xe5, 0xec, 0xa9, - 0xe8, 0xbf, 0xd0, 0x60, 0x49, 0x45, 0xca, 0xbe, 0x7c, 0xd9, 0x83, 0x5c, 0xde, 0x19, 0x28, 0x33, - 0xf2, 0x2b, 0x0d, 0xae, 0xa4, 0x86, 0x36, 0xd5, 0x8a, 0x4f, 0x91, 0x94, 0x5a, 0x1c, 0xf9, 0x29, - 0x8a, 0xa3, 0x09, 0x95, 0x5d, 0xcf, 0x09, 0x1c, 0xd3, 0x75, 0xbe, 0x4f, 0xe8, 0xe5, 0xbd, 0x94, - 0xfe, 0x07, 0x0d, 0xe6, 0x15, 0x0f, 0x86, 0x1e, 0xc0, 0x1c, 0x27, 0x2c, 0xc7, 0xeb, 0xc8, 0x3e, - 0x2f, 0xe3, 0x91, 0xa9, 0x80, 0x24, 0xe3, 0x6a, 0x87, 0x48, 0x38, 0x82, 0x44, 0x6d, 0x28, 0x52, - 0xc2, 0xfa, 0x6e, 0x20, 0xb9, 0xfa, 0x6a, 0xc6, 0xc3, 0x2d, 0x30, 0x83, 0x3e, 0x0b, 0x49, 0x0d, - 0x0b, 0x7f, 0x2c, 0x71, 0xf4, 0x3f, 0xe7, 0xa0, 0x7a, 0xdb, 0x3c, 0x24, 0xee, 0x01, 0x71, 0x89, - 0x15, 0xf8, 0x14, 0xfd, 0x00, 0x2a, 0x5d, 0x33, 0xb0, 0x8e, 0x85, 0x34, 0xea, 0x56, 0x5b, 0xd9, - 0x02, 0xa5, 0x90, 0x1a, 0x7b, 0x09, 0xcc, 0x8e, 0x17, 0xd0, 0x81, 0xf1, 0x92, 0x1c, 0x58, 0x45, - 0xd1, 0x60, 0x35, 0x9a, 0xb8, 0x62, 0x88, 0xef, 0x9d, 0x47, 0x3d, 0x7e, 0x94, 0x4e, 0x7f, 0xb3, - 0x49, 0xa5, 0x80, 0xc9, 0x7b, 0x7d, 0x87, 0x92, 0x2e, 0xf1, 0x82, 0xe4, 0x8a, 0xb1, 0x37, 0x82, - 0x8f, 0xc7, 0x22, 0xae, 0xdc, 0x80, 0xa5, 0xd1, 0xe4, 0xd1, 0x12, 0xe4, 0x4f, 0xc8, 0x20, 0xac, - 0x05, 0xcc, 0x7f, 0xa2, 0x2b, 0x50, 0x38, 0x35, 0xdd, 0xbe, 0xe4, 0x1f, 0x1c, 0x7e, 0x5c, 0xcf, - 0x5d, 0xd3, 0xf4, 0xdf, 0x68, 0x50, 0x9b, 0x94, 0x08, 0xfa, 0x94, 0x02, 0x64, 0x54, 0x64, 0x56, - 0xf9, 0x77, 0xc8, 0x20, 0x44, 0xdd, 0x81, 0x92, 0xdf, 0xe3, 0x97, 0x42, 0x9f, 0xca, 0x3a, 0x7f, - 0x23, 0xaa, 0xdd, 0x7d, 0x29, 0x3f, 0x1f, 0xd6, 0x5f, 0x4e, 0xc1, 0x47, 0x0a, 0x1c, 0xbb, 0xf2, - 0x13, 0x4d, 0xe4, 0xc3, 0x4f, 0xd9, 0xf8, 0x44, 0xbb, 0x27, 0x24, 0x58, 0x6a, 0xf4, 0xdf, 0x6b, - 0x30, 0x2b, 0x9a, 0xc4, 0x07, 0x50, 0xe2, 0xf3, 0x67, 0x9b, 0x81, 0x29, 0xf2, 0xca, 0x7c, 0x3d, - 0xe1, 0xde, 0x7b, 0x24, 0x30, 0x93, 0xfd, 0x15, 0x49, 0x70, 0x8c, 0x88, 0x30, 0x14, 0x9c, 0x80, - 0x74, 0xa3, 0x85, 0x7c, 0x73, 0x22, 0xb4, 0xbc, 0x1c, 0x37, 0xb0, 0xf9, 0x70, 0xe7, 0x51, 0x40, - 0x3c, 0xbe, 0x18, 0x09, 0x19, 0xec, 0x72, 0x0c, 0x1c, 0x42, 0xe9, 0xbf, 0xd5, 0x20, 0x0e, 0xc5, - 0xb7, 0x3b, 0x23, 0xee, 0xd1, 0x6d, 0xc7, 0x3b, 0x91, 0xd3, 0x1a, 0xa7, 0x73, 0x20, 0xe5, 0x38, - 0xb6, 0xb8, 0xe8, 0x40, 0xcc, 0x4d, 0x77, 0x20, 0xf2, 0x80, 0x96, 0xef, 0x05, 0x8e, 0xd7, 0x1f, - 0xe3, 0x97, 0x6d, 0x29, 0xc7, 0xb1, 0x85, 0xfe, 0xcf, 0x1c, 0x54, 0x78, 0xae, 0xd1, 0x89, 0xfc, - 0x25, 0xa8, 0xba, 0xea, 0xea, 0xc9, 0x9c, 0x5f, 0x96, 0x10, 0xe9, 0xfd, 0x88, 0xd3, 0xb6, 0xdc, - 0xf9, 0xc8, 0x21, 0xae, 0x1d, 0x3b, 0xe7, 0xd2, 0xce, 0x37, 0x55, 0x25, 0x4e, 0xdb, 0x72, 0x9e, - 0x7d, 0xc8, 0xeb, 0x5a, 0x76, 0x5e, 0xf1, 0xd4, 0x7e, 0x93, 0x0b, 0x71, 0xa8, 0xbb, 0x68, 0x7e, - 0x66, 0xa7, 0x9c, 0x9f, 0xeb, 0xb0, 0xc0, 0x17, 0xd2, 0xef, 0x07, 0x51, 0x7b, 0x5a, 0x10, 0x4d, - 0x12, 0x3a, 0x1b, 0xd6, 0x17, 0xde, 0x4d, 0x69, 0xf0, 0x88, 0x25, 0xcf, 0xd1, 0x75, 0xba, 0x4e, - 0x50, 0x9b, 0x13, 0x2e, 0x71, 0x8e, 0xb7, 0xb9, 0x10, 0x87, 0xba, 0xd4, 0x02, 0x94, 0x2e, 0x5d, - 0x80, 0xf7, 0xa0, 0xbc, 0xe7, 0x58, 0xd4, 0xe7, 0x91, 0xf9, 0x31, 0xc2, 0x52, 0x3d, 0x73, 0x4c, - 0xb7, 0x51, 0x46, 0x91, 0x9e, 0xa7, 0xe2, 0x99, 0x9e, 0x1f, 0x76, 0xc6, 0x85, 0x24, 0x95, 0x3b, - 0x5c, 0x88, 0x43, 0xdd, 0xf5, 0x2b, 0xfc, 0xf4, 0xf8, 0xe9, 0x93, 0xfa, 0xcc, 0xe3, 0x27, 0xf5, - 0x99, 0x0f, 0x9e, 0xc8, 0x93, 0xe4, 0xef, 0x00, 0xb0, 0x7f, 0xf8, 0x3d, 0x62, 0x85, 0x15, 0x7a, - 0xf9, 0xad, 0x9c, 0x77, 0x04, 0xf2, 0x31, 0x48, 0xdc, 0x60, 0x73, 0x23, 0x1d, 0x81, 0xa2, 0xc3, - 0x29, 0x4b, 0xd4, 0x84, 0x72, 0x7c, 0x53, 0x97, 0xd5, 0xb8, 0x2c, 0xdd, 0xca, 0xf1, 0x75, 0x1e, - 0x27, 0x36, 0xa9, 0xed, 0x32, 0x7b, 0xe9, 0x76, 0x31, 0x20, 0xdf, 0x77, 0x6c, 0xb1, 0x80, 0x65, - 0xe3, 0x73, 0x11, 0x5d, 0xdd, 0xdd, 0x6d, 0x9d, 0x0f, 0xeb, 0xaf, 0x4e, 0x7a, 0xe6, 0x0a, 0x06, - 0x3d, 0xc2, 0x1a, 0x77, 0x77, 0x5b, 0x98, 0x3b, 0x5f, 0x54, 0x52, 0xc5, 0x29, 0x4b, 0x6a, 0x13, - 0x40, 0x8e, 0x9a, 0x7b, 0x87, 0xb5, 0x11, 0xbf, 0x5a, 0xdc, 0x8a, 0x35, 0x58, 0xb1, 0x42, 0x0c, - 0x96, 0x2d, 0x7e, 0xe1, 0x72, 0x7c, 0x8f, 0x2f, 0x3d, 0x0b, 0xcc, 0x6e, 0x78, 0x6f, 0xaf, 0x6c, - 0x7e, 0x26, 0x1b, 0xbf, 0x71, 0x37, 0xe3, 0x15, 0x19, 0x66, 0x79, 0x7b, 0x14, 0x0c, 0x8f, 0xe3, - 0x23, 0x1f, 0x96, 0x6d, 0x79, 0xe9, 0x48, 0x82, 0x96, 0xa7, 0x0e, 0xfa, 0x32, 0x0f, 0xd8, 0x1a, - 0x05, 0xc2, 0xe3, 0xd8, 0xe8, 0x3b, 0xb0, 0x12, 0x09, 0xc7, 0x6f, 0x7e, 0xe2, 0x25, 0x21, 0x6f, - 0xac, 0xf2, 0x0b, 0x6c, 0x6b, 0xa2, 0x15, 0x7e, 0x0e, 0x02, 0xb2, 0xa1, 0xe8, 0x86, 0xbd, 0x40, - 0x45, 0xf0, 0xf7, 0x97, 0xb3, 0x8d, 0x22, 0xa9, 0xfe, 0x86, 0xda, 0x03, 0xc4, 0x37, 0x1b, 0x79, - 0xfc, 0x4b, 0x6c, 0xf4, 0x08, 0x2a, 0xa6, 0xe7, 0xf9, 0x81, 0x19, 0xde, 0x45, 0xe7, 0x45, 0xa8, - 0xad, 0xa9, 0x43, 0x6d, 0x25, 0x18, 0x23, 0x3d, 0x87, 0xa2, 0xc1, 0x6a, 0x28, 0xf4, 0x10, 0x16, - 0xfd, 0x87, 0x1e, 0xa1, 0x98, 0x1c, 0x11, 0x4a, 0x3c, 0x8b, 0xb0, 0x5a, 0x55, 0x44, 0xff, 0x7c, - 0xc6, 0xe8, 0x29, 0xe7, 0xa4, 0xa4, 0xd3, 0x72, 0x86, 0x47, 0xa3, 0xa0, 0x06, 0xc0, 0x91, 0xe3, - 0xc9, 0xce, 0xb1, 0xb6, 0x90, 0x3c, 0x3d, 0xdd, 0x8c, 0xa5, 0x58, 0xb1, 0x40, 0x5f, 0x80, 0x8a, - 0xe5, 0xf6, 0x59, 0x40, 0xc2, 0x37, 0xae, 0x45, 0xb1, 0x83, 0xe2, 0xf1, 0x6d, 0x27, 0x2a, 0xac, - 0xda, 0xa1, 0x63, 0x98, 0x77, 0x94, 0x16, 0xb5, 0xb6, 0x24, 0x6a, 0x71, 0x73, 0xea, 0xbe, 0x94, - 0x19, 0x4b, 0x9c, 0x89, 0x54, 0x09, 0x4e, 0x21, 0xaf, 0x7c, 0x11, 0x2a, 0xff, 0x66, 0xc7, 0xc4, - 0x3b, 0xae, 0xd1, 0xa5, 0x9b, 0xaa, 0xe3, 0xfa, 0x63, 0x0e, 0x16, 0xd2, 0x13, 0x1e, 0xdf, 0x4c, - 0xb4, 0x89, 0x6f, 0x96, 0x11, 0x2b, 0xe7, 0x27, 0xb2, 0xb2, 0x24, 0xbf, 0xd9, 0x17, 0x21, 0xbf, - 0x4d, 0x00, 0xb3, 0xe7, 0x44, 0xbc, 0x17, 0xf2, 0x68, 0xcc, 0x5c, 0xc9, 0xcb, 0x17, 0x56, 0xac, - 0xc4, 0xab, 0xa4, 0xef, 0x05, 0xd4, 0x77, 0x5d, 0x42, 0x05, 0x57, 0x96, 0xe4, 0xab, 0x64, 0x2c, - 0xc5, 0x8a, 0x05, 0xba, 0x09, 0xe8, 0xd0, 0xf5, 0xad, 0x13, 0x31, 0x05, 0xd1, 0x3e, 0x17, 0x2c, - 0x59, 0x0a, 0xdf, 0x84, 0x8c, 0x31, 0x2d, 0xbe, 0xc0, 0x43, 0x9f, 0x83, 0x42, 0x9b, 0x37, 0x01, - 0xfa, 0x3e, 0xa4, 0x9f, 0x73, 0xd0, 0x8d, 0x70, 0x26, 0xb4, 0xf8, 0xbd, 0x65, 0xba, 0x59, 0xd0, - 0xaf, 0x42, 0x19, 0xfb, 0x7e, 0xd0, 0x36, 0x83, 0x63, 0x86, 0xea, 0x50, 0xe8, 0xf1, 0x1f, 0xf2, - 0xdd, 0x4b, 0xbc, 0xff, 0x0a, 0x0d, 0x0e, 0xe5, 0xfa, 0xcf, 0x35, 0x78, 0x65, 0xe2, 0x7b, 0x1b, - 0x9f, 0x51, 0x2b, 0xfe, 0x92, 0x29, 0xc5, 0x33, 0x9a, 0xd8, 0x61, 0xc5, 0x8a, 0xf7, 0x4d, 0xa9, - 0x47, 0xba, 0xd1, 0xbe, 0x29, 0x15, 0x0d, 0xa7, 0x6d, 0xf5, 0x7f, 0xe4, 0xa0, 0x18, 0x5e, 0xa2, - 0xfe, 0xc3, 0xad, 0xf2, 0xeb, 0x50, 0x64, 0x22, 0x8e, 0x4c, 0x2f, 0x66, 0xcb, 0x30, 0x3a, 0x96, - 0x5a, 0xde, 0xc4, 0x74, 0x09, 0x63, 0x66, 0x27, 0x2a, 0xde, 0xb8, 0x89, 0xd9, 0x0b, 0xc5, 0x38, - 0xd2, 0xa3, 0xb7, 0xf9, 0x9d, 0xd1, 0x64, 0x71, 0x17, 0xb7, 0x1a, 0x41, 0x62, 0x21, 0x3d, 0x1f, - 0xd6, 0xe7, 0x25, 0xb8, 0xf8, 0xc6, 0xd2, 0x1a, 0xdd, 0x87, 0x39, 0x9b, 0x04, 0xa6, 0xe3, 0x86, - 0xcd, 0x5b, 0xe6, 0x87, 0xc1, 0x10, 0xac, 0x15, 0xba, 0x1a, 0x15, 0x9e, 0x93, 0xfc, 0xc0, 0x11, - 0x20, 0xdf, 0x78, 0x96, 0x6f, 0x87, 0xcf, 0xf4, 0x85, 0x64, 0xe3, 0x6d, 0xfb, 0x36, 0xc1, 0x42, - 0xa3, 0x3f, 0xd6, 0xa0, 0x12, 0x22, 0x6d, 0x9b, 0x7d, 0x46, 0xd0, 0x46, 0x3c, 0x8a, 0x70, 0xb9, - 0xa3, 0x33, 0x79, 0xf6, 0xdd, 0x41, 0x8f, 0x9c, 0x0f, 0xeb, 0x65, 0x61, 0xc6, 0x3f, 0xe2, 0x01, - 0x28, 0x73, 0x94, 0xbb, 0x64, 0x8e, 0x5e, 0x83, 0x82, 0x68, 0x94, 0xe5, 0x64, 0xc6, 0x8d, 0x9e, - 0x68, 0xa6, 0x71, 0xa8, 0xd3, 0x3f, 0xce, 0x41, 0x35, 0x35, 0xb8, 0x0c, 0x5d, 0x5d, 0xfc, 0xb0, - 0x91, 0xcb, 0xf0, 0x58, 0x36, 0xf9, 0x9f, 0x2b, 0xdf, 0x82, 0xa2, 0xc5, 0xc7, 0x17, 0xfd, 0x77, - 0x6b, 0x63, 0x9a, 0xa5, 0x10, 0x33, 0x93, 0x54, 0x92, 0xf8, 0x64, 0x58, 0x02, 0xa2, 0x5b, 0xb0, - 0x4c, 0x49, 0x40, 0x07, 0x5b, 0x47, 0x01, 0xa1, 0x6a, 0xb7, 0x5e, 0x48, 0xfa, 0x1e, 0x3c, 0x6a, - 0x80, 0xc7, 0x7d, 0x22, 0xaa, 0x2c, 0xbe, 0x00, 0x55, 0xea, 0x2e, 0xcc, 0xfe, 0x17, 0x7b, 0xf4, - 0x6f, 0x43, 0x39, 0xe9, 0xa2, 0x3e, 0xe1, 0x90, 0xfa, 0x77, 0xa1, 0xc4, 0xab, 0x31, 0xea, 0xfe, - 0x2f, 0x39, 0x89, 0xd2, 0x67, 0x44, 0x2e, 0xcb, 0x19, 0xa1, 0xbf, 0x05, 0xd5, 0xbb, 0x3d, 0x7b, - 0xca, 0x7f, 0x27, 0x6c, 0x42, 0xf8, 0x8f, 0x36, 0x4e, 0xc1, 0xe1, 0xa5, 0x5c, 0xa1, 0x60, 0xf5, - 0x86, 0xad, 0xbc, 0x8a, 0xfd, 0x58, 0x03, 0x10, 0x37, 0xc4, 0x9d, 0x53, 0xe2, 0x05, 0x7c, 0x34, - 0x7c, 0xd9, 0x46, 0x47, 0x23, 0xf6, 0x9e, 0xd0, 0xa0, 0xbb, 0x50, 0xf4, 0x45, 0x4b, 0x26, 0x9f, - 0xa9, 0xa6, 0xbc, 0xf1, 0xc7, 0xa5, 0x1a, 0xf6, 0x75, 0x58, 0x82, 0x19, 0xeb, 0x4f, 0x9f, 0xad, - 0xce, 0x7c, 0xf8, 0x6c, 0x75, 0xe6, 0xa3, 0x67, 0xab, 0x33, 0xef, 0x9f, 0xad, 0x6a, 0x4f, 0xcf, - 0x56, 0xb5, 0x0f, 0xcf, 0x56, 0xb5, 0x8f, 0xce, 0x56, 0xb5, 0x8f, 0xcf, 0x56, 0xb5, 0xc7, 0x7f, - 0x5d, 0x9d, 0xb9, 0x9f, 0x3b, 0xdd, 0xf8, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x3d, 0xb9, - 0xdd, 0x54, 0x20, 0x00, 0x00, + // 2643 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x39, 0x4d, 0x6c, 0x23, 0x49, + 0xd5, 0x69, 0x3b, 0x76, 0xec, 0xe7, 0x38, 0x3f, 0xb5, 0xb3, 0xdf, 0xe7, 0xb5, 0x44, 0x9c, 0xed, + 0x45, 0xab, 0x2c, 0xcc, 0xda, 0x24, 0xc3, 0xae, 0x86, 0x01, 0x16, 0xe2, 0x38, 0x99, 0x0d, 0x3b, + 0xd9, 0x44, 0x95, 0x99, 0x41, 0x0c, 0x23, 0x44, 0xc7, 0x5d, 0x71, 0x9a, 0xd8, 0xdd, 0xbd, 0x55, + 0xed, 0xcc, 0x04, 0x0e, 0xec, 0x01, 0x04, 0x48, 0x08, 0xcd, 0x91, 0x13, 0xda, 0x11, 0x5c, 0xb8, + 0x72, 0xe2, 0xc4, 0x69, 0x25, 0xe6, 0xb8, 0x12, 0x97, 0x3d, 0x20, 0x6b, 0x27, 0x20, 0xc1, 0x8d, + 0x7b, 0x0e, 0x08, 0xd5, 0x4f, 0x77, 0x57, 0xdb, 0xf1, 0xa4, 0xcd, 0x2c, 0x88, 0x53, 0xdc, 0xef, + 0xbf, 0xde, 0x7b, 0xf5, 0xde, 0xab, 0x17, 0xd8, 0x39, 0xbe, 0xce, 0xea, 0x8e, 0xd7, 0x38, 0xee, + 0x1f, 0x10, 0xea, 0x92, 0x80, 0xb0, 0xc6, 0x09, 0x71, 0x6d, 0x8f, 0x36, 0x14, 0xc2, 0xf2, 0x9d, + 0x9e, 0xd5, 0x3e, 0x72, 0x5c, 0x42, 0x4f, 0x1b, 0xfe, 0x71, 0x87, 0x03, 0x58, 0xa3, 0x47, 0x02, + 0xab, 0x71, 0xb2, 0xda, 0xe8, 0x10, 0x97, 0x50, 0x2b, 0x20, 0x76, 0xdd, 0xa7, 0x5e, 0xe0, 0xa1, + 0xcf, 0x4a, 0xae, 0xba, 0xce, 0x55, 0xf7, 0x8f, 0x3b, 0x1c, 0xc0, 0xea, 0x9c, 0xab, 0x7e, 0xb2, + 0x5a, 0x7d, 0xbd, 0xe3, 0x04, 0x47, 0xfd, 0x83, 0x7a, 0xdb, 0xeb, 0x35, 0x3a, 0x5e, 0xc7, 0x6b, + 0x08, 0xe6, 0x83, 0xfe, 0xa1, 0xf8, 0x12, 0x1f, 0xe2, 0x97, 0x14, 0x5a, 0x1d, 0x6b, 0x0a, 0xed, + 0xbb, 0x81, 0xd3, 0x23, 0xc3, 0x56, 0x54, 0xdf, 0xbc, 0x8c, 0x81, 0xb5, 0x8f, 0x48, 0xcf, 0x1a, + 0xe6, 0x33, 0xff, 0x98, 0x85, 0xc2, 0xfa, 0xde, 0xf6, 0x4d, 0xea, 0xf5, 0x7d, 0xb4, 0x0c, 0xd3, + 0xae, 0xd5, 0x23, 0x15, 0x63, 0xd9, 0x58, 0x29, 0x36, 0x67, 0x9f, 0x0c, 0x6a, 0x53, 0x67, 0x83, + 0xda, 0xf4, 0xbb, 0x56, 0x8f, 0x60, 0x81, 0x41, 0x5d, 0x28, 0x9c, 0x10, 0xca, 0x1c, 0xcf, 0x65, + 0x95, 0xcc, 0x72, 0x76, 0xa5, 0xb4, 0xf6, 0x56, 0x3d, 0xcd, 0xf9, 0xeb, 0x42, 0xc1, 0x5d, 0xc9, + 0xba, 0xe5, 0xd1, 0x96, 0xc3, 0xda, 0xde, 0x09, 0xa1, 0xa7, 0xcd, 0x05, 0xa5, 0xa5, 0xa0, 0x90, + 0x0c, 0x47, 0x1a, 0xd0, 0x8f, 0x0c, 0x58, 0xf0, 0x29, 0x39, 0x24, 0x94, 0x12, 0x5b, 0xe1, 0x2b, + 0xd9, 0x65, 0xe3, 0x53, 0x50, 0x5b, 0x51, 0x6a, 0x17, 0xf6, 0x86, 0xe4, 0xe3, 0x11, 0x8d, 0xe8, + 0xd7, 0x06, 0x54, 0x19, 0xa1, 0x27, 0x84, 0xae, 0xdb, 0x36, 0x25, 0x8c, 0x35, 0x4f, 0x37, 0xba, + 0x0e, 0x71, 0x83, 0x8d, 0xed, 0x16, 0x66, 0x95, 0x69, 0xe1, 0x87, 0xaf, 0xa5, 0x33, 0x68, 0x7f, + 0x9c, 0x9c, 0xa6, 0xa9, 0x2c, 0xaa, 0x8e, 0x25, 0x61, 0xf8, 0x19, 0x66, 0x98, 0x87, 0x30, 0x1b, + 0x06, 0xf2, 0x96, 0xc3, 0x02, 0x74, 0x17, 0xf2, 0x1d, 0xfe, 0xc1, 0x2a, 0x86, 0x30, 0xb0, 0x9e, + 0xce, 0xc0, 0x50, 0x46, 0x73, 0x4e, 0xd9, 0x93, 0x17, 0x9f, 0x0c, 0x2b, 0x69, 0xe6, 0xcf, 0xa6, + 0xa1, 0xb4, 0xbe, 0xb7, 0x8d, 0x09, 0xf3, 0xfa, 0xb4, 0x4d, 0x52, 0x24, 0xcd, 0x1a, 0x00, 0xff, + 0xcb, 0x7c, 0xab, 0x4d, 0xec, 0x4a, 0x66, 0xd9, 0x58, 0x29, 0x34, 0x91, 0xa2, 0x83, 0x77, 0x23, + 0x0c, 0xd6, 0xa8, 0xb8, 0xd4, 0x63, 0xc7, 0xb5, 0x45, 0xb4, 0x35, 0xa9, 0xef, 0x38, 0xae, 0x8d, + 0x05, 0x06, 0xdd, 0x82, 0xdc, 0x09, 0xa1, 0x07, 0xdc, 0xff, 0x3c, 0x21, 0x3e, 0x9f, 0xee, 0x78, + 0x77, 0x39, 0x4b, 0xb3, 0x78, 0x36, 0xa8, 0xe5, 0xc4, 0x4f, 0x2c, 0x85, 0xa0, 0x3a, 0x00, 0x3b, + 0xf2, 0x68, 0x20, 0xcc, 0xa9, 0xe4, 0x96, 0xb3, 0x2b, 0xc5, 0xe6, 0x1c, 0xb7, 0x6f, 0x3f, 0x82, + 0x62, 0x8d, 0x02, 0x5d, 0x87, 0x59, 0xe6, 0xb8, 0x9d, 0x7e, 0xd7, 0xa2, 0x1c, 0x50, 0xc9, 0x0b, + 0x3b, 0xaf, 0x28, 0x3b, 0x67, 0xf7, 0x35, 0x1c, 0x4e, 0x50, 0x72, 0x4d, 0x6d, 0x2b, 0x20, 0x1d, + 0x8f, 0x3a, 0x84, 0x55, 0x66, 0x62, 0x4d, 0x1b, 0x11, 0x14, 0x6b, 0x14, 0xe8, 0x15, 0xc8, 0x09, + 0xcf, 0x57, 0x0a, 0x42, 0x45, 0x59, 0xa9, 0xc8, 0x89, 0xb0, 0x60, 0x89, 0x43, 0xaf, 0xc1, 0x8c, + 0xba, 0x35, 0x95, 0xa2, 0x20, 0x9b, 0x57, 0x64, 0x33, 0x61, 0x5a, 0x87, 0x78, 0xf4, 0x0d, 0x40, + 0x2c, 0xf0, 0xa8, 0xd5, 0x21, 0x0a, 0xf5, 0xb6, 0xc5, 0x8e, 0x2a, 0x20, 0xb8, 0xaa, 0x8a, 0x0b, + 0xed, 0x8f, 0x50, 0xe0, 0x0b, 0xb8, 0xcc, 0xdf, 0x19, 0x30, 0xaf, 0xe5, 0x82, 0xc8, 0xbb, 0xeb, + 0x30, 0xdb, 0xd1, 0x6e, 0x9d, 0xca, 0x8b, 0xc8, 0x33, 0xfa, 0x8d, 0xc4, 0x09, 0x4a, 0x44, 0xa0, + 0x48, 0x95, 0xa4, 0xb0, 0xba, 0xac, 0xa6, 0x4e, 0xda, 0xd0, 0x86, 0x58, 0x93, 0x06, 0x64, 0x38, + 0x96, 0x6c, 0xfe, 0xcd, 0x10, 0x09, 0x1c, 0xd6, 0x1b, 0xb4, 0xa2, 0xd5, 0x34, 0x43, 0x84, 0x63, + 0x76, 0x4c, 0x3d, 0xba, 0xa4, 0x10, 0x64, 0xfe, 0x27, 0x0a, 0xc1, 0x8d, 0xc2, 0x2f, 0x3f, 0xa8, + 0x4d, 0xbd, 0xff, 0xe7, 0xe5, 0x29, 0xf3, 0x1a, 0x94, 0x37, 0x28, 0xb1, 0x02, 0xb2, 0xeb, 0x07, + 0xe2, 0x00, 0x26, 0xe4, 0x6d, 0x7a, 0x8a, 0xfb, 0xae, 0x3a, 0x28, 0xf0, 0xfb, 0xdd, 0x12, 0x10, + 0xac, 0x30, 0xe6, 0x4f, 0xb2, 0x50, 0x6e, 0x91, 0x2e, 0x89, 0xb9, 0xb6, 0x00, 0x75, 0xa8, 0xd5, + 0x26, 0x7b, 0x84, 0x3a, 0x9e, 0xbd, 0x4f, 0xda, 0x9e, 0x6b, 0x33, 0x11, 0xd7, 0x6c, 0xf3, 0xff, + 0x78, 0xb6, 0xdc, 0x1c, 0xc1, 0xe2, 0x0b, 0x38, 0x50, 0x17, 0xca, 0x3e, 0x15, 0xbf, 0x9d, 0x40, + 0x75, 0x10, 0x7e, 0x73, 0xaf, 0xa5, 0x73, 0xd8, 0x9e, 0xce, 0xda, 0x5c, 0x3c, 0x1b, 0xd4, 0xca, + 0x09, 0x10, 0x4e, 0x0a, 0x47, 0x5f, 0x87, 0x05, 0x8f, 0xfa, 0x47, 0x96, 0xdb, 0x22, 0x3e, 0x71, + 0x6d, 0xe2, 0x06, 0x4c, 0x54, 0x93, 0x42, 0xf3, 0x0a, 0xaf, 0xfb, 0xbb, 0x43, 0x38, 0x3c, 0x42, + 0x8d, 0xee, 0xc1, 0xa2, 0x4f, 0x3d, 0xdf, 0xea, 0x58, 0x5c, 0xe2, 0x9e, 0xd7, 0x75, 0xda, 0xa7, + 0xa2, 0xda, 0x14, 0x9b, 0x57, 0xcf, 0x06, 0xb5, 0xc5, 0xbd, 0x61, 0xe4, 0xf9, 0xa0, 0xf6, 0x82, + 0x70, 0x1d, 0x87, 0xc4, 0x48, 0x3c, 0x2a, 0x46, 0x8b, 0x44, 0x6e, 0x6c, 0x24, 0xb6, 0xa1, 0xd0, + 0xea, 0x53, 0xc1, 0x85, 0xbe, 0x0a, 0x05, 0x5b, 0xfd, 0x56, 0x9e, 0x7f, 0x39, 0x6c, 0x9c, 0x21, + 0xcd, 0xf9, 0xa0, 0x56, 0xe6, 0xad, 0xbe, 0x1e, 0x02, 0x70, 0xc4, 0x62, 0xde, 0x87, 0xf2, 0xe6, + 0x43, 0xdf, 0xa3, 0x41, 0x18, 0xd3, 0x57, 0x21, 0x4f, 0x04, 0x40, 0x48, 0x2b, 0xc4, 0xd5, 0x5e, + 0x92, 0x61, 0x85, 0xe5, 0xd5, 0x87, 0x3c, 0xb4, 0xda, 0x81, 0x2a, 0xdb, 0x51, 0xf5, 0xd9, 0xe4, + 0x40, 0x2c, 0x71, 0xe6, 0x87, 0x06, 0xe4, 0xb7, 0x1c, 0xd2, 0xb5, 0x19, 0xba, 0x0d, 0xd9, 0x9e, + 0xe5, 0xab, 0x96, 0xf3, 0x46, 0xba, 0xc8, 0x4a, 0xd6, 0xfa, 0x8e, 0xe5, 0x6f, 0xba, 0x01, 0x3d, + 0x6d, 0x96, 0x94, 0x92, 0xec, 0x8e, 0xe5, 0x63, 0x2e, 0xae, 0x6a, 0x43, 0x21, 0xc4, 0xa2, 0x05, + 0xc8, 0x1e, 0x93, 0x53, 0x59, 0x56, 0x30, 0xff, 0x89, 0x9a, 0x90, 0x3b, 0xb1, 0xba, 0x7d, 0xa2, + 0xf2, 0xe9, 0xea, 0x24, 0x5a, 0xb1, 0x64, 0xbd, 0x91, 0xb9, 0x6e, 0x98, 0xbb, 0x00, 0x37, 0x49, + 0xe4, 0xa1, 0x75, 0x98, 0x0f, 0x6b, 0x46, 0xb2, 0x94, 0xfd, 0xbf, 0x32, 0x6f, 0x1e, 0x27, 0xd1, + 0x78, 0x98, 0xde, 0xbc, 0x0f, 0x45, 0x51, 0xee, 0x78, 0xd7, 0x8a, 0xeb, 0xb8, 0xf1, 0x8c, 0x3a, + 0x1e, 0xb6, 0xbd, 0xcc, 0xb8, 0xb6, 0xa7, 0xdd, 0xee, 0x2e, 0x94, 0x25, 0x6f, 0xd8, 0x89, 0x53, + 0x69, 0xb8, 0x0a, 0x85, 0xd0, 0x4c, 0xa5, 0x25, 0x9a, 0xc0, 0x42, 0x41, 0x38, 0xa2, 0xd0, 0xb4, + 0x1d, 0x41, 0xa2, 0x74, 0xa7, 0x53, 0xa6, 0xb5, 0xa5, 0xcc, 0xb3, 0xdb, 0x92, 0xa6, 0xe9, 0x87, + 0x50, 0x19, 0x37, 0xb6, 0x3d, 0x47, 0x73, 0x49, 0x6f, 0x8a, 0xf9, 0x0b, 0x03, 0x16, 0x74, 0x49, + 0xe9, 0xc3, 0x97, 0x5e, 0xc9, 0xe5, 0x03, 0x8e, 0xe6, 0x91, 0x5f, 0x19, 0x70, 0x25, 0x71, 0xb4, + 0x89, 0x22, 0x3e, 0x81, 0x51, 0x7a, 0x72, 0x64, 0x27, 0x48, 0x8e, 0x06, 0x94, 0xb6, 0x5d, 0x27, + 0x70, 0xac, 0xae, 0xf3, 0x7d, 0x42, 0x2f, 0x1f, 0x09, 0xcd, 0x3f, 0x18, 0x30, 0xab, 0x71, 0x30, + 0x74, 0x1f, 0x66, 0x78, 0xdd, 0x75, 0xdc, 0x8e, 0xaa, 0x1d, 0x29, 0x3b, 0xbf, 0x26, 0x24, 0x3e, + 0xd7, 0x9e, 0x94, 0x84, 0x43, 0x91, 0x68, 0x0f, 0xf2, 0x94, 0xb0, 0x7e, 0x37, 0x98, 0xac, 0x44, + 0xec, 0x07, 0x56, 0xd0, 0x67, 0xb2, 0x36, 0x63, 0xc1, 0x8f, 0x95, 0x1c, 0xf3, 0x4f, 0x19, 0x28, + 0xdf, 0xb2, 0x0e, 0x48, 0x77, 0x9f, 0x74, 0x49, 0x3b, 0xf0, 0x28, 0xfa, 0x01, 0x94, 0x7a, 0x56, + 0xd0, 0x3e, 0x12, 0xd0, 0x70, 0xe8, 0x6e, 0xa5, 0x53, 0x94, 0x90, 0x54, 0xdf, 0x89, 0xc5, 0xc8, + 0x82, 0xf8, 0x82, 0x3a, 0x58, 0x49, 0xc3, 0x60, 0x5d, 0x9b, 0x78, 0x29, 0x89, 0xef, 0xcd, 0x87, + 0x3e, 0x9f, 0x08, 0x26, 0x7f, 0xa0, 0x25, 0x4c, 0xc0, 0xe4, 0xbd, 0xbe, 0x43, 0x49, 0x8f, 0xb8, + 0x41, 0xfc, 0x52, 0xda, 0x19, 0x92, 0x8f, 0x47, 0x34, 0x56, 0xdf, 0x82, 0x85, 0x61, 0xe3, 0x2f, + 0xa8, 0xd7, 0x57, 0xf4, 0x7a, 0x5d, 0xd4, 0x2b, 0xf0, 0x6f, 0x0c, 0xa8, 0x8c, 0x33, 0x04, 0x7d, + 0x46, 0x13, 0x14, 0xf7, 0x88, 0x77, 0xc8, 0xa9, 0x94, 0xba, 0x09, 0x05, 0xcf, 0xe7, 0x6f, 0x5b, + 0x8f, 0xaa, 0x3c, 0x7f, 0x2d, 0xcc, 0xdd, 0x5d, 0x05, 0x3f, 0x1f, 0xd4, 0x5e, 0x4c, 0x88, 0x0f, + 0x11, 0x38, 0x62, 0xe5, 0x8d, 0x59, 0xd8, 0xc3, 0x87, 0x85, 0xa8, 0x31, 0xdf, 0x15, 0x10, 0xac, + 0x30, 0xe6, 0xef, 0x0d, 0x98, 0x16, 0xb3, 0xee, 0x7d, 0x28, 0x70, 0xff, 0xd9, 0x56, 0x60, 0x09, + 0xbb, 0x52, 0xbf, 0xb2, 0x38, 0xf7, 0x0e, 0x09, 0xac, 0xf8, 0x7e, 0x85, 0x10, 0x1c, 0x49, 0x44, + 0x18, 0x72, 0x4e, 0x40, 0x7a, 0x61, 0x20, 0x5f, 0x1f, 0x2b, 0x5a, 0xbd, 0xf1, 0xeb, 0xd8, 0x7a, + 0xb0, 0xf9, 0x30, 0x20, 0x2e, 0x0f, 0x46, 0x5c, 0x0c, 0xb6, 0xb9, 0x0c, 0x2c, 0x45, 0x99, 0xbf, + 0x35, 0x20, 0x52, 0xc5, 0xaf, 0x3b, 0x23, 0xdd, 0xc3, 0x5b, 0x8e, 0x7b, 0xac, 0xdc, 0x1a, 0x99, + 0xb3, 0xaf, 0xe0, 0x38, 0xa2, 0xb8, 0xa8, 0x21, 0x66, 0x26, 0x6b, 0x88, 0x5c, 0x61, 0xdb, 0x73, + 0x03, 0xc7, 0xed, 0x8f, 0xd4, 0x97, 0x0d, 0x05, 0xc7, 0x11, 0x85, 0xf9, 0xcf, 0x0c, 0x94, 0xb8, + 0xad, 0x61, 0x47, 0xfe, 0x32, 0x94, 0xbb, 0x7a, 0xf4, 0x94, 0xcd, 0x2f, 0x2a, 0x11, 0xc9, 0xfb, + 0x88, 0x93, 0xb4, 0x9c, 0xf9, 0x90, 0x77, 0xfc, 0x88, 0x39, 0x93, 0x64, 0xde, 0xd2, 0x91, 0x38, + 0x49, 0xcb, 0xeb, 0xec, 0x03, 0x9e, 0xd7, 0x6a, 0x80, 0x8c, 0x5c, 0xfb, 0x4d, 0x0e, 0xc4, 0x12, + 0x77, 0x91, 0x7f, 0xa6, 0x27, 0xf4, 0xcf, 0x0d, 0x98, 0xe3, 0x81, 0xf4, 0xfa, 0x41, 0x38, 0x65, + 0xe7, 0xc4, 0xac, 0x87, 0xce, 0x06, 0xb5, 0xb9, 0xdb, 0x09, 0x0c, 0x1e, 0xa2, 0xe4, 0x36, 0x76, + 0x9d, 0x9e, 0x13, 0x54, 0x66, 0x04, 0x4b, 0x64, 0xe3, 0x2d, 0x0e, 0xc4, 0x12, 0x97, 0x08, 0x40, + 0xe1, 0xd2, 0x00, 0xfc, 0x3d, 0x03, 0x68, 0xc7, 0x72, 0xad, 0x0e, 0xb1, 0xe5, 0xb4, 0x24, 0x6f, + 0xf4, 0x6b, 0x30, 0xd3, 0x13, 0xd0, 0x30, 0x02, 0x51, 0xe1, 0x95, 0xc4, 0x14, 0x87, 0x78, 0xb4, + 0x03, 0x45, 0x79, 0xb3, 0xe2, 0x6c, 0x69, 0x28, 0xe2, 0xe2, 0x6e, 0x88, 0x38, 0x1f, 0xd4, 0xaa, + 0x09, 0x35, 0x11, 0xe6, 0xf6, 0xa9, 0x4f, 0x70, 0x2c, 0x01, 0xad, 0x01, 0x58, 0xbe, 0xa3, 0x6f, + 0x82, 0x8a, 0xf1, 0x26, 0x21, 0x7e, 0xd3, 0x61, 0x8d, 0x0a, 0xbd, 0x0d, 0xd3, 0xdc, 0x53, 0x6a, + 0x4d, 0xf0, 0xb9, 0x74, 0xf7, 0x93, 0xfb, 0xba, 0x59, 0xe0, 0x4d, 0x8b, 0xff, 0xc2, 0x42, 0x02, + 0xba, 0x07, 0x79, 0x91, 0x16, 0x32, 0x2a, 0x13, 0x0e, 0x9a, 0xe2, 0xd5, 0xa1, 0xa6, 0xe4, 0xf3, + 0xe8, 0x17, 0x56, 0x12, 0xcd, 0xf7, 0xa0, 0xb8, 0xe3, 0xb4, 0xa9, 0xc7, 0xd5, 0x71, 0x07, 0xb3, + 0xc4, 0x2b, 0x2b, 0x72, 0x70, 0x18, 0xfc, 0x10, 0xcf, 0xa3, 0xee, 0x5a, 0xae, 0x27, 0xdf, 0x52, + 0xb9, 0x38, 0xea, 0xef, 0x72, 0x20, 0x96, 0xb8, 0x1b, 0x57, 0x78, 0xa3, 0xfe, 0xe9, 0xe3, 0xda, + 0xd4, 0xa3, 0xc7, 0xb5, 0xa9, 0x0f, 0x1e, 0xab, 0xa6, 0xfd, 0xd7, 0x12, 0xc0, 0xee, 0xc1, 0xf7, + 0x48, 0x5b, 0x16, 0x83, 0xcb, 0xf7, 0x38, 0x7c, 0xf8, 0x52, 0xeb, 0x43, 0xb1, 0xf3, 0xc8, 0x0c, + 0x0d, 0x5f, 0x1a, 0x0e, 0x27, 0x28, 0x51, 0x03, 0x8a, 0xd1, 0x6e, 0x47, 0x85, 0x6d, 0x31, 0x4c, + 0x83, 0x68, 0x01, 0x84, 0x63, 0x9a, 0x44, 0x65, 0x9a, 0xbe, 0xb4, 0x32, 0x35, 0x21, 0xdb, 0x77, + 0x6c, 0x11, 0x95, 0x62, 0xf3, 0x0b, 0x61, 0x67, 0xb8, 0xb3, 0xdd, 0x3a, 0x1f, 0xd4, 0x5e, 0x1e, + 0xb7, 0x18, 0x0d, 0x4e, 0x7d, 0xc2, 0xea, 0x77, 0xb6, 0x5b, 0x98, 0x33, 0x5f, 0x74, 0x7b, 0xf3, + 0x13, 0xde, 0xde, 0x35, 0x00, 0x75, 0x6a, 0xce, 0x2d, 0xaf, 0x61, 0x94, 0x9d, 0x37, 0x23, 0x0c, + 0xd6, 0xa8, 0x10, 0x83, 0xc5, 0x36, 0x7f, 0xa2, 0xf3, 0x64, 0x77, 0x7a, 0x84, 0x05, 0x56, 0x4f, + 0x6e, 0x7a, 0x26, 0x4b, 0xd5, 0x97, 0x94, 0x9a, 0xc5, 0x8d, 0x61, 0x61, 0x78, 0x54, 0x3e, 0xf2, + 0x60, 0xd1, 0x56, 0xcf, 0xd4, 0x58, 0x69, 0x71, 0x62, 0xa5, 0x2f, 0x72, 0x85, 0xad, 0x61, 0x41, + 0x78, 0x54, 0x36, 0xfa, 0x0e, 0x54, 0x43, 0xe0, 0xe8, 0xae, 0x40, 0xec, 0x9e, 0xb2, 0xcd, 0xa5, + 0xb3, 0x41, 0xad, 0xda, 0x1a, 0x4b, 0x85, 0x9f, 0x21, 0x01, 0xd9, 0x90, 0xef, 0xca, 0xb1, 0xab, + 0x24, 0x5a, 0xe5, 0x57, 0xd2, 0x9d, 0x22, 0xce, 0xfe, 0xba, 0x3e, 0x6e, 0x45, 0x6f, 0x61, 0x35, + 0x69, 0x29, 0xd9, 0xe8, 0x21, 0x94, 0x2c, 0xd7, 0xf5, 0x02, 0x4b, 0x6e, 0x2f, 0x66, 0x85, 0xaa, + 0xf5, 0x89, 0x55, 0xad, 0xc7, 0x32, 0x86, 0xc6, 0x3b, 0x0d, 0x83, 0x75, 0x55, 0xe8, 0x01, 0xcc, + 0x7b, 0x0f, 0x5c, 0x42, 0x31, 0x39, 0x24, 0x94, 0xb8, 0x6d, 0xc2, 0x2a, 0x65, 0xa1, 0xfd, 0x8b, + 0x29, 0xb5, 0x27, 0x98, 0xe3, 0x94, 0x4e, 0xc2, 0x19, 0x1e, 0xd6, 0x82, 0xea, 0x00, 0x87, 0x8e, + 0xab, 0x86, 0xf4, 0xca, 0x5c, 0xbc, 0xac, 0xdc, 0x8a, 0xa0, 0x58, 0xa3, 0x40, 0x6f, 0x40, 0xa9, + 0xdd, 0xed, 0xb3, 0x80, 0xc8, 0xad, 0xe8, 0xbc, 0xb8, 0x41, 0xd1, 0xf9, 0x36, 0x62, 0x14, 0xd6, + 0xe9, 0xd0, 0x11, 0xcc, 0x3a, 0xda, 0x6b, 0xa0, 0xb2, 0x20, 0x72, 0x71, 0x6d, 0xe2, 0x27, 0x00, + 0x6b, 0x2e, 0xf0, 0x4a, 0xa4, 0x43, 0x70, 0x42, 0x32, 0xea, 0x43, 0xb9, 0xa7, 0xb7, 0x9a, 0xca, + 0xa2, 0xf0, 0xe3, 0xf5, 0x74, 0xaa, 0x46, 0x9b, 0x61, 0x3c, 0x40, 0x24, 0x70, 0x38, 0xa9, 0xa5, + 0xfa, 0x25, 0x28, 0xfd, 0x9b, 0x33, 0x31, 0x9f, 0xa9, 0x87, 0x33, 0x66, 0xa2, 0x99, 0xfa, 0xc3, + 0x0c, 0xcc, 0x25, 0xe3, 0x1c, 0xbd, 0x3d, 0x8d, 0xb1, 0xcb, 0xf5, 0xb0, 0x19, 0x64, 0xc7, 0x36, + 0x03, 0x55, 0x73, 0xa7, 0x9f, 0xa7, 0xe6, 0x26, 0xdb, 0x79, 0x2e, 0x55, 0x3b, 0xaf, 0x03, 0xf0, + 0xf9, 0x84, 0x7a, 0xdd, 0x2e, 0xa1, 0xa2, 0x44, 0x17, 0xd4, 0xfa, 0x3c, 0x82, 0x62, 0x8d, 0x02, + 0x6d, 0x01, 0x3a, 0xe8, 0x7a, 0xed, 0x63, 0xe1, 0x82, 0xb0, 0xbc, 0x88, 0xe2, 0x5c, 0x90, 0xcb, + 0xcb, 0xe6, 0x08, 0x16, 0x5f, 0xc0, 0x61, 0xce, 0x40, 0x6e, 0x8f, 0x8f, 0x79, 0xe6, 0x3e, 0xcc, + 0x8a, 0x1f, 0x13, 0xec, 0x54, 0x51, 0x0d, 0x72, 0x87, 0x5e, 0xb8, 0x71, 0x29, 0xc8, 0x7f, 0x3f, + 0x6c, 0x71, 0x00, 0x96, 0x70, 0x73, 0x17, 0x92, 0xcb, 0x4c, 0xf4, 0x96, 0x74, 0xaf, 0x11, 0x6d, + 0x1b, 0x27, 0x73, 0xad, 0x79, 0x15, 0x8a, 0xd8, 0xf3, 0x82, 0x3d, 0x2b, 0x38, 0x62, 0x5c, 0xbd, + 0xcf, 0x7f, 0x28, 0x0b, 0x85, 0x7a, 0x81, 0xc1, 0x12, 0x6e, 0xfe, 0xdc, 0x80, 0x97, 0xc6, 0x6e, + 0x9b, 0x79, 0x98, 0xda, 0xd1, 0x97, 0x32, 0x29, 0x0a, 0x53, 0x4c, 0x87, 0x35, 0x2a, 0x3e, 0x6e, + 0x27, 0x56, 0xd4, 0xc3, 0xe3, 0x76, 0x42, 0x1b, 0x4e, 0xd2, 0x9a, 0xff, 0xc8, 0x40, 0x5e, 0xbe, + 0xbd, 0xff, 0xc3, 0x2f, 0xac, 0x57, 0x21, 0xcf, 0x84, 0x1e, 0x65, 0x5e, 0x54, 0xf9, 0xa5, 0x76, + 0xac, 0xb0, 0x62, 0xe2, 0x25, 0x8c, 0x59, 0x9d, 0xf0, 0x46, 0xc4, 0x13, 0xaf, 0x04, 0xe3, 0x10, + 0x8f, 0xde, 0x84, 0x3c, 0x25, 0x16, 0x8b, 0x86, 0xff, 0xa5, 0x50, 0x24, 0x16, 0xd0, 0xf3, 0x41, + 0x6d, 0x56, 0x09, 0x17, 0xdf, 0x58, 0x51, 0xa3, 0x7b, 0x30, 0x63, 0x93, 0xc0, 0x72, 0xba, 0xe1, + 0x74, 0x79, 0x6d, 0x92, 0x1d, 0x45, 0x4b, 0xb2, 0x36, 0x4b, 0xdc, 0x26, 0xf5, 0x81, 0x43, 0x81, + 0xfc, 0x36, 0xb7, 0x3d, 0x5b, 0xfe, 0x93, 0x2a, 0x17, 0xdf, 0xe6, 0x0d, 0xcf, 0x26, 0x58, 0x60, + 0xcc, 0x47, 0x06, 0x94, 0xa4, 0xa4, 0x0d, 0xab, 0xcf, 0x08, 0x5a, 0x8d, 0x4e, 0x21, 0xc3, 0x1d, + 0xce, 0x17, 0xd3, 0x7c, 0x22, 0x3f, 0x1f, 0xd4, 0x8a, 0x82, 0x4c, 0x8c, 0xe7, 0xe1, 0x01, 0x34, + 0x1f, 0x65, 0x2e, 0xf1, 0xd1, 0x2b, 0x90, 0x13, 0x63, 0xaf, 0x72, 0x66, 0x34, 0xb4, 0x8a, 0x62, + 0x89, 0x25, 0xce, 0xfc, 0x24, 0x03, 0xe5, 0xc4, 0xe1, 0x52, 0x4c, 0xa8, 0xd1, 0x3e, 0x2c, 0x93, + 0x62, 0xc7, 0x3a, 0xfe, 0x5f, 0x8b, 0xdf, 0x82, 0x7c, 0x9b, 0x9f, 0x2f, 0xfc, 0xdf, 0xee, 0xea, + 0x24, 0xa1, 0x10, 0x9e, 0x89, 0x33, 0x49, 0x7c, 0x32, 0xac, 0x04, 0xa2, 0x9b, 0xb0, 0x48, 0x49, + 0x40, 0x4f, 0xd7, 0x0f, 0x03, 0x42, 0xf5, 0x47, 0x5e, 0x2e, 0x9e, 0xe1, 0xf0, 0x30, 0x01, 0x1e, + 0xe5, 0x09, 0xeb, 0x6f, 0xfe, 0x39, 0xea, 0xaf, 0xd9, 0x85, 0xe9, 0xff, 0xe2, 0x7b, 0xe3, 0xdb, + 0x50, 0x8c, 0x27, 0xc2, 0x4f, 0x59, 0xa5, 0xf9, 0x5d, 0x28, 0xf0, 0x6c, 0x0c, 0x5f, 0x32, 0x97, + 0xb4, 0xb7, 0x64, 0xe3, 0xc9, 0xa4, 0x69, 0x3c, 0xe6, 0x35, 0x28, 0xdf, 0xf1, 0xed, 0x09, 0xff, + 0x99, 0xb6, 0x06, 0xf2, 0xdf, 0xcc, 0xbc, 0x04, 0xcb, 0x5d, 0x8e, 0x56, 0x82, 0xf5, 0xc5, 0x8c, + 0xb6, 0x4c, 0xfd, 0xb1, 0x01, 0x20, 0x16, 0x0b, 0x9b, 0x27, 0xc4, 0x0d, 0xf8, 0x69, 0x78, 0xd8, + 0x86, 0x4f, 0x23, 0xee, 0x9e, 0xc0, 0xa0, 0x3b, 0x90, 0xf7, 0xc4, 0x78, 0xa9, 0xb6, 0x9b, 0x13, + 0x2e, 0x8a, 0xa2, 0x54, 0x95, 0x33, 0x2a, 0x56, 0xc2, 0x9a, 0x2b, 0x4f, 0x9e, 0x2e, 0x4d, 0x7d, + 0xf4, 0x74, 0x69, 0xea, 0xe3, 0xa7, 0x4b, 0x53, 0xef, 0x9f, 0x2d, 0x19, 0x4f, 0xce, 0x96, 0x8c, + 0x8f, 0xce, 0x96, 0x8c, 0x8f, 0xcf, 0x96, 0x8c, 0x4f, 0xce, 0x96, 0x8c, 0x47, 0x7f, 0x59, 0x9a, + 0xba, 0x97, 0x39, 0x59, 0xfd, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xb1, 0xa3, 0x81, 0x52, + 0x23, 0x00, 0x00, } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto index 47d8495f1c1..ceb50f51223 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto @@ -202,6 +202,13 @@ message ExportOptions { optional bool exact = 2; } +// Fields stores a set of fields in a data structure like a Trie. +// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff +message Fields { + // Map stores a set of fields in a data structure like a Trie. + map map = 1; +} + // GetOptions is the standard query options to the standard REST get call. message GetOptions { // When specified: @@ -436,6 +443,31 @@ message ListOptions { optional string continue = 8; } +// ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource +// that the fieldset applies to. +message ManagedFieldsEntry { + // Manager is an identifier of the workflow managing these fields. + optional string manager = 1; + + // Operation is the type of operation which lead to this ManagedFieldsEntry being created. + // The only valid values for this field are 'Apply' and 'Update'. + optional string operation = 2; + + // APIVersion defines the version of this resource that this field set + // applies to. The format is "group/version" just like the top-level + // APIVersion field. It is necessary to track the version of a field + // set because it cannot be automatically converted. + optional string apiVersion = 3; + + // Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply' + // +optional + optional Time time = 4; + + // Fields identifies a set of fields. + // +optional + optional Fields fields = 5; +} + // MicroTime is version of Time with microsecond level precision. // // +protobuf.options.marshal=false @@ -617,6 +649,19 @@ message ObjectMeta { // This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request. // +optional optional string clusterName = 15; + + // ManagedFields maps workflow-id and version to the set of fields + // that are managed by that workflow. This is mostly for internal + // housekeeping, and users typically shouldn't need to set or + // understand this field. A workflow can be the user's name, a + // controller's name, or the name of a specific apply path like + // "ci-cd". The set of fields is always in the version that the + // workflow used when modifying the object. + // + // This field is alpha and can be changed or removed without notice. + // + // +optional + repeated ManagedFieldsEntry managedFields = 17; } // OwnerReference contains enough information to let you identify an owning @@ -656,6 +701,24 @@ message OwnerReference { message Patch { } +// PatchOptions may be provided when patching an API object. +// PatchOptions is meant to be a superset of UpdateOptions. +message PatchOptions { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + repeated string dryRun = 1; + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. Force + // flag must be unset for non-apply patch requests. + // +optional + optional bool force = 2; +} + // Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. message Preconditions { // Specifies the target UID. @@ -841,6 +904,7 @@ message TypeMeta { } // UpdateOptions may be provided when updating an API object. +// All fields in UpdateOptions should also be present in PatchOptions. message UpdateOptions { // When present, indicates that modifications should not be // persisted. An invalid or unrecognized dryRun directive will diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go index 14a17f1bf57..df26788c422 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_swagger_doc_generated.go @@ -118,6 +118,14 @@ func (ExportOptions) SwaggerDoc() map[string]string { return map_ExportOptions } +var map_Fields = map[string]string{ + "": "Fields stores a set of fields in a data structure like a Trie. To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff", +} + +func (Fields) SwaggerDoc() map[string]string { + return map_Fields +} + var map_GetOptions = map[string]string{ "": "GetOptions is the standard query options to the standard REST get call.", "resourceVersion": "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", @@ -213,6 +221,19 @@ func (ListOptions) SwaggerDoc() map[string]string { return map_ListOptions } +var map_ManagedFieldsEntry = map[string]string{ + "": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "manager": "Manager is an identifier of the workflow managing these fields.", + "operation": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "apiVersion": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "time": "Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply'", + "fields": "Fields identifies a set of fields.", +} + +func (ManagedFieldsEntry) SwaggerDoc() map[string]string { + return map_ManagedFieldsEntry +} + var map_ObjectMeta = map[string]string{ "": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", "name": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names", @@ -231,6 +252,7 @@ var map_ObjectMeta = map[string]string{ "initializers": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.\n\nDEPRECATED - initializers are an alpha field and will be removed in v1.15.", "finalizers": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed.", "clusterName": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.", + "managedFields": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.\n\nThis field is alpha and can be changed or removed without notice.", } func (ObjectMeta) SwaggerDoc() map[string]string { @@ -259,6 +281,16 @@ func (Patch) SwaggerDoc() map[string]string { return map_Patch } +var map_PatchOptions = map[string]string{ + "": "PatchOptions may be provided when patching an API object. PatchOptions is meant to be a superset of UpdateOptions.", + "dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "force": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", +} + +func (PatchOptions) SwaggerDoc() map[string]string { + return map_PatchOptions +} + var map_Preconditions = map[string]string{ "": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", "uid": "Specifies the target UID.", @@ -337,7 +369,7 @@ func (TypeMeta) SwaggerDoc() map[string]string { } var map_UpdateOptions = map[string]string{ - "": "UpdateOptions may be provided when updating an API object.", + "": "UpdateOptions may be provided when updating an API object. All fields in UpdateOptions should also be present in PatchOptions.", "dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go index 10845993e26..5b09ac54c48 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go @@ -312,6 +312,29 @@ func (in *ExportOptions) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Fields) DeepCopyInto(out *Fields) { + *out = *in + if in.Map != nil { + in, out := &in.Map, &out.Map + *out = make(map[string]Fields, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Fields. +func (in *Fields) DeepCopy() *Fields { + if in == nil { + return nil + } + out := new(Fields) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GetOptions) DeepCopyInto(out *GetOptions) { *out = *in @@ -624,6 +647,31 @@ func (in *ListOptions) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagedFieldsEntry) DeepCopyInto(out *ManagedFieldsEntry) { + *out = *in + if in.Time != nil { + in, out := &in.Time, &out.Time + *out = (*in).DeepCopy() + } + if in.Fields != nil { + in, out := &in.Fields, &out.Fields + *out = new(Fields) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedFieldsEntry. +func (in *ManagedFieldsEntry) DeepCopy() *ManagedFieldsEntry { + if in == nil { + return nil + } + out := new(ManagedFieldsEntry) + in.DeepCopyInto(out) + return out +} + // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroTime. func (in *MicroTime) DeepCopy() *MicroTime { if in == nil { @@ -678,6 +726,13 @@ func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ManagedFields != nil { + in, out := &in.ManagedFields, &out.ManagedFields + *out = make([]ManagedFieldsEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -733,6 +788,41 @@ func (in *Patch) DeepCopy() *Patch { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchOptions) DeepCopyInto(out *PatchOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Force != nil { + in, out := &in.Force, &out.Force + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchOptions. +func (in *PatchOptions) DeepCopy() *PatchOptions { + if in == nil { + return nil + } + out := new(PatchOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PatchOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Preconditions) DeepCopyInto(out *Preconditions) { *out = *in diff --git a/staging/src/k8s.io/apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiserver/Godeps/Godeps.json index cad4fc800c8..56d6073bea3 100644 --- a/staging/src/k8s.io/apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiserver/Godeps/Godeps.json @@ -448,7 +448,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -1954,6 +1954,10 @@ "ImportPath": "k8s.io/kube-openapi/pkg/handler", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/schemaconv", + "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" + }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" @@ -1962,6 +1966,10 @@ "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/util/proto/testing", + "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" + }, { "ImportPath": "k8s.io/client-go/discovery", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2066,6 +2074,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/cli-runtime/Godeps/Godeps.json b/staging/src/k8s.io/cli-runtime/Godeps/Godeps.json index bea1493a2f5..488ed64c92a 100644 --- a/staging/src/k8s.io/cli-runtime/Godeps/Godeps.json +++ b/staging/src/k8s.io/cli-runtime/Godeps/Godeps.json @@ -48,7 +48,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -614,6 +614,26 @@ "ImportPath": "k8s.io/utils/integer", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/client-go/Godeps/Godeps.json b/staging/src/k8s.io/client-go/Godeps/Godeps.json index 182f7b4148d..481a4070f99 100644 --- a/staging/src/k8s.io/client-go/Godeps/Godeps.json +++ b/staging/src/k8s.io/client-go/Godeps/Godeps.json @@ -96,7 +96,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -626,6 +626,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/cloud-provider/Godeps/Godeps.json b/staging/src/k8s.io/cloud-provider/Godeps/Godeps.json index 2959210ba93..13f12fe1a2e 100644 --- a/staging/src/k8s.io/cloud-provider/Godeps/Godeps.json +++ b/staging/src/k8s.io/cloud-provider/Godeps/Godeps.json @@ -44,7 +44,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -990,6 +990,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/csi-api/Godeps/Godeps.json b/staging/src/k8s.io/csi-api/Godeps/Godeps.json index 4bad4f3392e..a237fc94783 100644 --- a/staging/src/k8s.io/csi-api/Godeps/Godeps.json +++ b/staging/src/k8s.io/csi-api/Godeps/Godeps.json @@ -48,7 +48,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -558,6 +558,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json b/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json index 83c2a8625b0..7a497ef629d 100644 --- a/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json +++ b/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json @@ -160,7 +160,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -1078,6 +1078,14 @@ "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/negotiation", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1822,6 +1830,10 @@ "ImportPath": "k8s.io/kube-openapi/pkg/handler", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/schemaconv", + "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" + }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" @@ -1842,6 +1854,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/metrics/Godeps/Godeps.json b/staging/src/k8s.io/metrics/Godeps/Godeps.json index 787839a7b07..59413af373f 100644 --- a/staging/src/k8s.io/metrics/Godeps/Godeps.json +++ b/staging/src/k8s.io/metrics/Godeps/Godeps.json @@ -48,7 +48,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -526,6 +526,26 @@ "ImportPath": "k8s.io/utils/integer", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/node-api/Godeps/Godeps.json b/staging/src/k8s.io/node-api/Godeps/Godeps.json index 2ec3d9fcbd8..2b8827599e6 100644 --- a/staging/src/k8s.io/node-api/Godeps/Godeps.json +++ b/staging/src/k8s.io/node-api/Godeps/Godeps.json @@ -48,7 +48,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -558,6 +558,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json index 30abbf59cd5..f8f07ecee36 100644 --- a/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json @@ -152,7 +152,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -1042,6 +1042,14 @@ "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/endpoints/handlers/negotiation", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1774,6 +1782,10 @@ "ImportPath": "k8s.io/kube-openapi/pkg/handler", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/schemaconv", + "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" + }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", "Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03" @@ -1794,6 +1806,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json b/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json index 18d8ded2d56..47c64bc67dd 100644 --- a/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json +++ b/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json @@ -48,7 +48,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -578,6 +578,26 @@ "ImportPath": "k8s.io/utils/integer", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" diff --git a/staging/src/k8s.io/sample-controller/Godeps/Godeps.json b/staging/src/k8s.io/sample-controller/Godeps/Godeps.json index 724f5704af1..4ec020c7c3c 100644 --- a/staging/src/k8s.io/sample-controller/Godeps/Godeps.json +++ b/staging/src/k8s.io/sample-controller/Godeps/Godeps.json @@ -52,7 +52,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" + "Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1" }, { "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", @@ -1166,6 +1166,26 @@ "ImportPath": "k8s.io/utils/trace", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/merge", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/schema", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/typed", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, + { + "ImportPath": "sigs.k8s.io/structured-merge-diff/value", + "Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" From 6b2e4682fe883eebcaf1c1e43cf2957dde441174 Mon Sep 17 00:00:00 2001 From: jennybuckley Date: Fri, 1 Feb 2019 11:55:18 -0800 Subject: [PATCH 07/10] Minor fixes --- pkg/api/pod/util_test.go | 3 +- pkg/api/testing/defaulting_test.go | 1 + pkg/api/v1/persistentvolume/util_test.go | 2 + pkg/api/v1/pod/util_test.go | 3 +- .../pkg/apis/meta/v1/generated.proto | 10 ++ .../apimachinery/pkg/apis/meta/v1/meta.go | 19 +-- .../apimachinery/pkg/apis/meta/v1/types.go | 10 ++ .../pkg/endpoints/handlers/fieldmanager/BUILD | 1 + .../handlers/fieldmanager/fieldmanager.go | 34 +++-- .../fieldmanager/internal/managedfields.go | 15 +-- .../internal/managedfields_test.go | 8 +- .../fieldmanager/internal/pathelement_test.go | 46 +++---- .../fieldmanager/internal/typeconverter.go | 1 + .../internal/typeconverter_test.go | 77 ++++++++++- .../pkg/endpoints/handlers/rest_test.go | 3 - .../pkg/endpoints/patchhandler_test.go | 124 ------------------ .../apiserver/pkg/features/kube_features.go | 2 +- test/integration/auth/rbac_test.go | 77 +++++------ 18 files changed, 204 insertions(+), 232 deletions(-) diff --git a/pkg/api/pod/util_test.go b/pkg/api/pod/util_test.go index c2cdd1b9d89..0f0696c9cda 100644 --- a/pkg/api/pod/util_test.go +++ b/pkg/api/pod/util_test.go @@ -176,7 +176,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n case reflect.Ptr: resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) case reflect.Struct: - // Specifically skip ObjectMeta because it has recursive types + // ObjectMeta is generic and therefore should never have a field with a specific resource's name; + // it contains cycles so it's easiest to just skip it. if name == "ObjectMeta" { break } diff --git a/pkg/api/testing/defaulting_test.go b/pkg/api/testing/defaulting_test.go index dac954097dc..50453577272 100644 --- a/pkg/api/testing/defaulting_test.go +++ b/pkg/api/testing/defaulting_test.go @@ -49,6 +49,7 @@ func TestDefaulting(t *testing.T) { {Group: "", Version: "v1", Kind: "ConfigMap"}: {}, {Group: "", Version: "v1", Kind: "ConfigMapList"}: {}, {Group: "", Version: "v1", Kind: "Endpoints"}: {}, + {Group: "", Version: "v1", Kind: "EndpointsList"}: {}, {Group: "", Version: "v1", Kind: "Namespace"}: {}, {Group: "", Version: "v1", Kind: "NamespaceList"}: {}, {Group: "", Version: "v1", Kind: "Node"}: {}, diff --git a/pkg/api/v1/persistentvolume/util_test.go b/pkg/api/v1/persistentvolume/util_test.go index bf653821464..5a60dd1ce52 100644 --- a/pkg/api/v1/persistentvolume/util_test.go +++ b/pkg/api/v1/persistentvolume/util_test.go @@ -247,6 +247,8 @@ func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect. case reflect.Ptr: secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...) case reflect.Struct: + // ObjectMeta should not have any field with the word "secret" in it; + // it contains cycles so it's easiest to just skip it. if name == "ObjectMeta" { break } diff --git a/pkg/api/v1/pod/util_test.go b/pkg/api/v1/pod/util_test.go index b9f3544dc66..af05d73639b 100644 --- a/pkg/api/v1/pod/util_test.go +++ b/pkg/api/v1/pod/util_test.go @@ -340,7 +340,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n case reflect.Ptr: resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) case reflect.Struct: - // Specifically skip ObjectMeta because it has recursive types + // ObjectMeta is generic and therefore should never have a field with a specific resource's name; + // it contains cycles so it's easiest to just skip it. if name == "ObjectMeta" { break } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto index ceb50f51223..70fbe897917 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto @@ -206,6 +206,16 @@ message ExportOptions { // To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff message Fields { // Map stores a set of fields in a data structure like a Trie. + // + // Each key is either a '.' representing the field itself, and will always map to an empty set, + // or a string representing a sub-field or item. The string will follow one of these four formats: + // 'f:', where is the name of a field in a struct, or key in a map + // 'v:', where is the exact json formatted value of a list item + // 'i:', where is position of a item in a list + // 'k:', where is a map of a list item's key fields to their unique values + // If a key maps to an empty Fields value, the field that key represents is part of the set. + // + // The exact format is defined in k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal map map = 1; } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go index 7eecbd912dc..05f07adf1b8 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go @@ -64,7 +64,7 @@ type Object interface { GetClusterName() string SetClusterName(clusterName string) GetManagedFields() []ManagedFieldsEntry - SetManagedFields(lastApplied []ManagedFieldsEntry) + SetManagedFields(managedFields []ManagedFieldsEntry) } // ListMetaAccessor retrieves the list interface from an object @@ -168,16 +168,9 @@ func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { return m func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { meta.OwnerReferences = references } -func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName } -func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName } - -func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { - return meta.ManagedFields -} - -func (meta *ObjectMeta) SetManagedFields(ManagedFields []ManagedFieldsEntry) { - meta.ManagedFields = make([]ManagedFieldsEntry, len(ManagedFields)) - for i, value := range ManagedFields { - meta.ManagedFields[i] = value - } +func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName } +func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName } +func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields } +func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) { + meta.ManagedFields = managedFields } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go index 30c07984e0a..a85355a8333 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go @@ -1079,5 +1079,15 @@ const ( // To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff type Fields struct { // Map stores a set of fields in a data structure like a Trie. + // + // Each key is either a '.' representing the field itself, and will always map to an empty set, + // or a string representing a sub-field or item. The string will follow one of these four formats: + // 'f:', where is the name of a field in a struct, or key in a map + // 'v:', where is the exact json formatted value of a list item + // 'i:', where is position of a item in a list + // 'k:', where is a map of a list item's key fields to their unique values + // If a key maps to an empty Fields value, the field that key represents is part of the set. + // + // The exact format is defined in k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal Map map[string]Fields `json:",inline" protobuf:"bytes,1,rep,name=map"` } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD index ce474dbbc45..fed358126e6 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/BUILD @@ -8,6 +8,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go index f4c1cdd3e2b..773063890cc 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go @@ -21,6 +21,7 @@ import ( "time" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -82,9 +83,19 @@ func NewCRDFieldManager(objectConverter runtime.ObjectConvertor, objectDefaulter // use-case), and simply updates the managed fields in the output // object. func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) { + // If the object doesn't have metadata, we should just return without trying to + // set the managedFields at all, so creates/updates/patches will work normally. + if _, err := meta.Accessor(newObj); err != nil { + return newObj, nil + } + + // First try to decode the managed fields provided in the update, + // This is necessary to allow directly updating managed fields. managed, err := internal.DecodeObjectManagedFields(newObj) + // If the managed field is empty or we failed to decode it, - // let's try the live object + // let's try the live object. This is to prevent clients who + // don't understand managedFields from deleting it accidentally. if err != nil || len(managed) == 0 { managed, err = internal.DecodeObjectManagedFields(liveObj) if err != nil { @@ -99,13 +110,8 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r if err != nil { return nil, fmt.Errorf("failed to convert live object to proper version: %v", err) } - if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil { - return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err) - } - if err := internal.RemoveObjectManagedFields(newObjVersioned); err != nil { - return nil, fmt.Errorf("failed to remove managed fields from new obj: %v", err) - } - + internal.RemoveObjectManagedFields(liveObjVersioned) + internal.RemoveObjectManagedFields(newObjVersioned) newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned) if err != nil { return nil, fmt.Errorf("failed to create typed new object: %v", err) @@ -135,19 +141,23 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r // Apply is used when server-side apply is called, as it merges the // object and update the managed fields. func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) (runtime.Object, error) { + // If the object doesn't have metadata, apply isn't allowed. + if _, err := meta.Accessor(liveObj); err != nil { + return nil, fmt.Errorf("couldn't get accessor: %v", err) + } + managed, err := internal.DecodeObjectManagedFields(liveObj) if err != nil { return nil, fmt.Errorf("failed to decode managed fields: %v", err) } // We can assume that patchObj is already on the proper version: // it shouldn't have to be converted so that it's not defaulted. + // TODO (jennybuckley): Explicitly checkt that patchObj is in the proper version. liveObjVersioned, err := f.toVersioned(liveObj) if err != nil { return nil, fmt.Errorf("failed to convert live object to proper version: %v", err) } - if err := internal.RemoveObjectManagedFields(liveObjVersioned); err != nil { - return nil, fmt.Errorf("failed to remove managed fields from live obj: %v", err) - } + internal.RemoveObjectManagedFields(liveObjVersioned) patchObjTyped, err := f.typeConverter.YAMLToTyped(patch) if err != nil { @@ -211,5 +221,5 @@ func (f *FieldManager) buildManagerInfo(prefix string, operation metav1.ManagedF if managerInfo.Manager == "" { managerInfo.Manager = "unknown" } - return internal.DecodeManager(&managerInfo) + return internal.BuildManagerIdentifier(&managerInfo) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go index b2f32646233..0c20d97b92b 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go @@ -30,13 +30,12 @@ import ( // RemoveObjectManagedFields removes the ManagedFields from the object // before we merge so that it doesn't appear in the ManagedFields // recursively. -func RemoveObjectManagedFields(obj runtime.Object) error { +func RemoveObjectManagedFields(obj runtime.Object) { accessor, err := meta.Accessor(obj) if err != nil { - return fmt.Errorf("couldn't get accessor: %v", err) + panic(fmt.Sprintf("couldn't get accessor: %v", err)) } accessor.SetManagedFields(nil) - return nil } // DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields. @@ -46,7 +45,7 @@ func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, er } accessor, err := meta.Accessor(from) if err != nil { - return nil, fmt.Errorf("couldn't get accessor: %v", err) + panic(fmt.Sprintf("couldn't get accessor: %v", err)) } managed, err := decodeManagedFields(accessor.GetManagedFields()) @@ -60,7 +59,7 @@ func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, er func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedFields) error { accessor, err := meta.Accessor(obj) if err != nil { - return fmt.Errorf("couldn't get accessor: %v", err) + panic(fmt.Sprintf("couldn't get accessor: %v", err)) } managed, err := encodeManagedFields(fields) @@ -77,7 +76,7 @@ func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedField func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) { managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields)) for _, encodedVersionedSet := range encodedManagedFields { - manager, err := DecodeManager(&encodedVersionedSet) + manager, err := BuildManagerIdentifier(&encodedVersionedSet) if err != nil { return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err) } @@ -89,8 +88,8 @@ func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (mana return managedFields, nil } -// DecodeManager creates a manager identifier string from a ManagedFieldsEntry -func DecodeManager(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) { +// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry +func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) { encodedManagerCopy := *encodedManager // Never include the fields in the manager identifier diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go index 7f2d517f574..1712b3b23ae 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields_test.go @@ -25,8 +25,8 @@ import ( "sigs.k8s.io/yaml" ) -// TestRoundTripManagedFields will roundtrip ManagedFields from the format used by -// sigs.k8s.io/structured-merge-diff to the wire format (api format) and back +// TestRoundTripManagedFields will roundtrip ManagedFields from the wire format +// (api format) to the format used by sigs.k8s.io/structured-merge-diff and back func TestRoundTripManagedFields(t *testing.T) { tests := []string{ `- apiVersion: v1 @@ -153,7 +153,7 @@ func TestRoundTripManagedFields(t *testing.T) { } } -func TestDecodeManager(t *testing.T) { +func TestBuildManagerIdentifier(t *testing.T) { tests := []struct { managedFieldsEntry string expected string @@ -188,7 +188,7 @@ time: "2001-02-03T04:05:06Z" if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } - decoded, err := DecodeManager(&unmarshaled) + decoded, err := BuildManagerIdentifier(&unmarshaled) if err != nil { t.Fatalf("did not expect decoding error but got: %v", err) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go index 81b9dd41765..bd119e03c2e 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement_test.go @@ -20,20 +20,20 @@ import "testing" func TestPathElementRoundTrip(t *testing.T) { tests := []string{ - "i:0", - "i:1234", - "f:", - "f:spec", - "f:more-complicated-string", - "k:{\"name\":\"my-container\"}", - "k:{\"port\":\"8080\",\"protocol\":\"TCP\"}", - "k:{\"optionalField\":null}", - "k:{\"jsonField\":{\"A\":1,\"B\":null,\"C\":\"D\",\"E\":{\"F\":\"G\"}}}", - "k:{\"listField\":[\"1\",\"2\",\"3\"]}", - "v:null", - "v:\"some-string\"", - "v:1234", - "v:{\"some\":\"json\"}", + `i:0`, + `i:1234`, + `f:`, + `f:spec`, + `f:more-complicated-string`, + `k:{"name":"my-container"}`, + `k:{"port":"8080","protocol":"TCP"}`, + `k:{"optionalField":null}`, + `k:{"jsonField":{"A":1,"B":null,"C":"D","E":{"F":"G"}}}`, + `k:{"listField":["1","2","3"]}`, + `v:null`, + `v:"some-string"`, + `v:1234`, + `v:{"some":"json"}`, } for _, test := range tests { @@ -62,15 +62,15 @@ func TestPathElementIgnoreUnknown(t *testing.T) { func TestNewPathElementError(t *testing.T) { tests := []string{ - "", - "no-colon", - "i:index is not a number", - "i:1.23", - "i:", - "v:invalid json", - "v:", - "k:invalid json", - "k:{\"name\":invalid}", + ``, + `no-colon`, + `i:index is not a number`, + `i:1.23`, + `i:`, + `v:invalid json`, + `v:`, + `k:invalid json`, + `k:{"name":invalid}`, } for _, test := range tests { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go index b4974752dea..80ac84128ff 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go @@ -43,6 +43,7 @@ type TypeConverter interface { // // Note that this is not going to be sufficient for converting to/from // CRDs that have a schema defined (we don't support that schema yet). +// TODO(jennybuckley): Use the schema provided by a CRD if it exists. type DeducedTypeConverter struct{} var _ TypeConverter = DeducedTypeConverter{} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go index bced6d6328f..093ce64c945 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter_test.go @@ -17,8 +17,10 @@ limitations under the License. package internal_test import ( + "fmt" "path/filepath" "reflect" + "strings" "testing" "github.com/ghodss/yaml" @@ -30,7 +32,7 @@ import ( var fakeSchema = prototesting.Fake{ Path: filepath.Join( - "..", "..", "..", "..", "..", "..", "..", "..", "..", + strings.Repeat(".."+string(filepath.Separator), 9), "api", "openapi-spec", "swagger.json"), } @@ -49,7 +51,15 @@ func TestTypeConverter(t *testing.T) { t.Fatalf("Failed to build TypeConverter: %v", err) } - y := ` + dtc := internal.DeducedTypeConverter{} + + testCases := []struct { + name string + yaml string + }{ + { + name: "apps/v1.Deployment", + yaml: ` apiVersion: apps/v1 kind: Deployment metadata: @@ -69,8 +79,61 @@ spec: containers: - name: nginx image: nginx:1.15.4 -` +`, + }, { + name: "extensions/v1beta1.Deployment", + yaml: ` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 +`, + }, { + name: "v1.Pod", + yaml: ` +apiVersion: v1 +kind: Pod +metadata: + name: nginx-pod + labels: + app: nginx +spec: + containers: + - name: nginx + image: nginx:1.15.4 +`, + }, + } + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%v ObjectToTyped with TypeConverter", testCase.name), func(t *testing.T) { + testObjectToTyped(t, tc, testCase.yaml) + }) + t.Run(fmt.Sprintf("%v YAMLToTyped with TypeConverter", testCase.name), func(t *testing.T) { + testYAMLToTyped(t, tc, testCase.yaml) + }) + t.Run(fmt.Sprintf("%v ObjectToTyped with DeducedTypeConverter", testCase.name), func(t *testing.T) { + testObjectToTyped(t, dtc, testCase.yaml) + }) + } +} + +func testObjectToTyped(t *testing.T, tc internal.TypeConverter, y string) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { t.Fatalf("Failed to parse yaml object: %v", err) @@ -90,12 +153,18 @@ Original object: Final object: %#v`, obj, newObj) } +} +func testYAMLToTyped(t *testing.T, tc internal.TypeConverter, y string) { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { + t.Fatalf("Failed to parse yaml object: %v", err) + } yamlTyped, err := tc.YAMLToTyped([]byte(y)) if err != nil { t.Fatalf("Failed to convert yaml to typed: %v", err) } - newObj, err = tc.TypedToObject(yamlTyped) + newObj, err := tc.TypedToObject(yamlTyped) if err != nil { t.Fatalf("Failed to convert typed to object: %v", err) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go index 04febe03a86..f6690f86950 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go @@ -795,9 +795,6 @@ func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) { tc.Run(t) } -// TODO: Add test case for "apply with existing uid" verify it gives a conflict error, -// not a creation or an authz creation forbidden message - func TestHasUID(t *testing.T) { testcases := []struct { obj runtime.Object diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go index f96c80c8bd4..f27a702dffd 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/patchhandler_test.go @@ -25,10 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapitesting "k8s.io/apiserver/pkg/endpoints/testing" - genericfeatures "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/rest" - utilfeature "k8s.io/apiserver/pkg/util/feature" - utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" ) func TestPatch(t *testing.T) { @@ -152,124 +149,3 @@ func TestPatchRequiresMatchingName(t *testing.T) { t.Errorf("Unexpected response %#v", response) } } - -func TestPatchApply(t *testing.T) { - t.Skip("apply is being refactored") - defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() - storage := map[string]rest.Storage{} - item := &genericapitesting.Simple{ - ObjectMeta: metav1.ObjectMeta{ - Name: "id", - Namespace: "", - UID: "uid", - }, - Other: "bar", - } - simpleStorage := SimpleRESTStorage{item: *item} - storage["simple"] = &simpleStorage - handler := handle(storage) - server := httptest.NewServer(handler) - defer server.Close() - - client := http.Client{} - request, err := http.NewRequest( - "PATCH", - server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", - bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)), - ) - request.Header.Set("Content-Type", "application/apply-patch+yaml") - response, err := client.Do(request) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if response.StatusCode != http.StatusOK { - t.Errorf("Unexpected response %#v", response) - } - if simpleStorage.updated.Labels["test"] != "yes" { - t.Errorf(`Expected labels to have "test": "yes", found %q`, simpleStorage.updated.Labels["test"]) - } - if simpleStorage.updated.Other != "bar" { - t.Errorf(`Merge should have kept initial "bar" value for Other: %v`, simpleStorage.updated.Other) - } - if len(simpleStorage.updated.ObjectMeta.ManagedFields) == 0 { - t.Errorf(`Expected managedFields field to be set, but is empty`) - } -} - -func TestApplyAddsGVK(t *testing.T) { - t.Skip("apply is being refactored") - defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() - storage := map[string]rest.Storage{} - item := &genericapitesting.Simple{ - ObjectMeta: metav1.ObjectMeta{ - Name: "id", - Namespace: "", - UID: "uid", - }, - Other: "bar", - } - simpleStorage := SimpleRESTStorage{item: *item} - storage["simple"] = &simpleStorage - handler := handle(storage) - server := httptest.NewServer(handler) - defer server.Close() - - client := http.Client{} - request, err := http.NewRequest( - "PATCH", - server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", - bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)), - ) - request.Header.Set("Content-Type", "application/apply-patch+yaml") - response, err := client.Do(request) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if response.StatusCode != http.StatusOK { - t.Errorf("Unexpected response %#v", response) - } - // TODO: Need to fix this - expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}` - if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected { - t.Errorf( - `Expected managedFields field to be %q, got %q`, - expected, - simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion, - ) - } -} - -func TestApplyCreatesWithManagedFields(t *testing.T) { - t.Skip("apply is being refactored") - defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)() - storage := map[string]rest.Storage{} - simpleStorage := SimpleRESTStorage{} - storage["simple"] = &simpleStorage - handler := handle(storage) - server := httptest.NewServer(handler) - defer server.Close() - - client := http.Client{} - request, err := http.NewRequest( - "PATCH", - server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", - bytes.NewReader([]byte(`{"metadata":{"name":"id"}, "labels": {"test": "yes"}}`)), - ) - request.Header.Set("Content-Type", "application/apply-patch+yaml") - response, err := client.Do(request) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if response.StatusCode != http.StatusOK { - t.Errorf("Unexpected response %#v", response) - } - // TODO: Need to fix this - expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}` - if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected { - t.Errorf( - `Expected managedFields field to be %q, got %q`, - expected, - simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion, - ) - } -} diff --git a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go index 966e91ac97f..32a58fbad43 100644 --- a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go @@ -84,7 +84,7 @@ const ( DryRun utilfeature.Feature = "DryRun" // owner: @apelisse, @lavalamp - // alpha: v1.11 + // alpha: v1.14 // // Server-side apply. Merging happens on the server. ServerSideApply utilfeature.Feature = "ServerSideApply" diff --git a/test/integration/auth/rbac_test.go b/test/integration/auth/rbac_test.go index 963f872c86f..0d8403c32ab 100644 --- a/test/integration/auth/rbac_test.go +++ b/test/integration/auth/rbac_test.go @@ -30,6 +30,7 @@ import ( apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/authentication/request/bearertoken" "k8s.io/apiserver/pkg/authentication/token/tokenfile" @@ -482,40 +483,40 @@ func TestRBAC(t *testing.T) { {"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, }, }, - // { - // bootstrapRoles: bootstrapRoles{ - // clusterRoles: []rbacapi.ClusterRole{ - // { - // ObjectMeta: metav1.ObjectMeta{Name: "allow-all"}, - // Rules: []rbacapi.PolicyRule{ruleAllowAll}, - // }, - // { - // ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, - // Rules: []rbacapi.PolicyRule{ - // rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(), - // }, - // }, - // }, - // clusterRoleBindings: []rbacapi.ClusterRoleBinding{ - // { - // ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, - // Subjects: []rbacapi.Subject{ - // {Kind: "User", Name: "limitrange-patcher"}, - // }, - // RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"}, - // }, - // }, - // }, - // requests: []request{ - // // Create the namespace used later in the test - // {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated}, + { + bootstrapRoles: bootstrapRoles{ + clusterRoles: []rbacapi.ClusterRole{ + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all"}, + Rules: []rbacapi.PolicyRule{ruleAllowAll}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, + Rules: []rbacapi.PolicyRule{ + rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(), + }, + }, + }, + clusterRoleBindings: []rbacapi.ClusterRoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"}, + Subjects: []rbacapi.Subject{ + {Kind: "User", Name: "limitrange-patcher"}, + }, + RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"}, + }, + }, + }, + requests: []request{ + // Create the namespace used later in the test + {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated}, - // {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden}, - // {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated}, - // {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, - // {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, - // }, - // }, + {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden}, + {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated}, + {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, + {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK}, + }, + }, } for i, tc := range tests { @@ -535,6 +536,7 @@ func TestRBAC(t *testing.T) { "limitrange-patcher": {Name: "limitrange-patcher"}, "user-with-no-permissions": {Name: "user-with-no-permissions"}, })) + masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig() _, s, closeFn := framework.RunAMaster(masterConfig) defer closeFn() @@ -569,11 +571,10 @@ func TestRBAC(t *testing.T) { } req, err := http.NewRequest(r.verb, s.URL+path, body) - // TODO: Un-comment this when Apply works again - // if r.verb == "PATCH" { - // // For patch operations, use the apply content type - // req.Header.Add("Content-Type", string(types.ApplyPatchType)) - // } + if r.verb == "PATCH" { + // For patch operations, use the apply content type + req.Header.Add("Content-Type", string(types.ApplyPatchType)) + } if err != nil { t.Fatalf("failed to create request: %v", err) From 79f3135d2ea2884d2d8115dcdd93a0b454c87b6f Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Tue, 29 Jan 2019 16:19:52 -0800 Subject: [PATCH 08/10] Add object information on FieldManager failures --- staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go | 4 ++-- staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go | 4 ++-- staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go index f8c2dd4c347..cd95727fde9 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go @@ -137,13 +137,13 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte if scope.FieldManager != nil { liveObj, err := scope.Creater.New(scope.Kind) if err != nil { - scope.err(fmt.Errorf("failed to create new object: %v", err), w, req) + scope.err(fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err), w, req) return } obj, err = scope.FieldManager.Update(liveObj, obj, prefixFromUserAgent(req.UserAgent())) if err != nil { - scope.err(fmt.Errorf("failed to update object managed fields: %v", err), w, req) + scope.err(fmt.Errorf("failed to update object (Create for %v) managed fields: %v", scope.Kind, err), w, req) return } } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go index 2d0d5f08075..dd41e93eb9e 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go @@ -312,7 +312,7 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r if p.fieldManager != nil { if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, prefixFromUserAgent(p.userAgent)); err != nil { - return nil, fmt.Errorf("failed to update object managed fields: %v", err) + return nil, fmt.Errorf("failed to update object (json PATCH for %v) managed fields: %v", p.kind, err) } } return objToUpdate, nil @@ -374,7 +374,7 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru if p.fieldManager != nil { if newObj, err = p.fieldManager.Update(currentObject, newObj, prefixFromUserAgent(p.userAgent)); err != nil { - return nil, fmt.Errorf("failed to update object managed fields: %v", err) + return nil, fmt.Errorf("failed to update object (smp PATCH for %v) managed fields: %v", p.kind, err) } } return newObj, nil diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go index a57e913d5bb..4e0c17559f6 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go @@ -125,7 +125,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac if scope.FieldManager != nil { transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) { if obj, err = scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent())); err != nil { - return nil, fmt.Errorf("failed to update object managed fields: %v", err) + return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err) } return obj, nil }) From bbab0d6c5f9fe75388583772ef4f094e863f9627 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Wed, 30 Jan 2019 14:15:25 -0800 Subject: [PATCH 09/10] Avoid closure --- staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go index 4e0c17559f6..7289d9495f1 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go @@ -124,7 +124,8 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac transformers := []rest.TransformFunc{} if scope.FieldManager != nil { transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) { - if obj, err = scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent())); err != nil { + obj, err := scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent())) + if err != nil { return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err) } return obj, nil From fcd4985ce4732be7c4dfa62f8e54d92d53496e15 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Wed, 30 Jan 2019 19:01:03 -0800 Subject: [PATCH 10/10] Fix SubjectAccessReview Validation with ManagedFields --- pkg/apis/authorization/validation/validation.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/apis/authorization/validation/validation.go b/pkg/apis/authorization/validation/validation.go index 512c383078c..4179b8274b6 100644 --- a/pkg/apis/authorization/validation/validation.go +++ b/pkg/apis/authorization/validation/validation.go @@ -56,7 +56,9 @@ func ValidateSelfSubjectRulesReview(review *authorizationapi.SelfSubjectRulesRev func ValidateSubjectAccessReview(sar *authorizationapi.SubjectAccessReview) field.ErrorList { allErrs := ValidateSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec")) - if !apiequality.Semantic.DeepEqual(metav1.ObjectMeta{}, sar.ObjectMeta) { + objectMetaShallowCopy := sar.ObjectMeta + objectMetaShallowCopy.ManagedFields = nil + if !apiequality.Semantic.DeepEqual(metav1.ObjectMeta{}, objectMetaShallowCopy) { allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty`)) } return allErrs @@ -64,7 +66,9 @@ func ValidateSubjectAccessReview(sar *authorizationapi.SubjectAccessReview) fiel func ValidateSelfSubjectAccessReview(sar *authorizationapi.SelfSubjectAccessReview) field.ErrorList { allErrs := ValidateSelfSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec")) - if !apiequality.Semantic.DeepEqual(metav1.ObjectMeta{}, sar.ObjectMeta) { + objectMetaShallowCopy := sar.ObjectMeta + objectMetaShallowCopy.ManagedFields = nil + if !apiequality.Semantic.DeepEqual(metav1.ObjectMeta{}, objectMetaShallowCopy) { allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty`)) } return allErrs @@ -75,6 +79,7 @@ func ValidateLocalSubjectAccessReview(sar *authorizationapi.LocalSubjectAccessRe objectMetaShallowCopy := sar.ObjectMeta objectMetaShallowCopy.Namespace = "" + objectMetaShallowCopy.ManagedFields = nil if !apiequality.Semantic.DeepEqual(metav1.ObjectMeta{}, objectMetaShallowCopy) { allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty except for namespace`)) }