rebase: bump k8s.io/kubernetes from 1.26.2 to 1.27.2

Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2.
- [Release notes](https://github.com/kubernetes/kubernetes/releases)
- [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2)

---
updated-dependencies:
- dependency-name: k8s.io/kubernetes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-05-29 21:03:29 +00:00
committed by mergify[bot]
parent 0e79135419
commit 07b05616a0
1072 changed files with 208716 additions and 198880 deletions

View File

@ -118,6 +118,8 @@ func (cm *connectionManager) closeAll() {
// grpcTunnel implements Tunnel
type grpcTunnel struct {
stream client.ProxyService_ProxyClient
sendLock sync.Mutex
recvLock sync.Mutex
clientConn clientConn
pendingDial pendingDialManager
conns connectionManager
@ -130,6 +132,11 @@ type grpcTunnel struct {
// serving.
done chan struct{}
// started is an atomic bool represented as a 0 or 1, and set to true when a single-use tunnel has been started (dialed).
// started should only be accessed through atomic methods.
// TODO: switch this to an atomic.Bool once the client is exclusively buit with go1.19+
started uint32
// closing is an atomic bool represented as a 0 or 1, and set to true when the tunnel is being closed.
// closing should only be accessed through atomic methods.
// TODO: switch this to an atomic.Bool once the client is exclusively buit with go1.19+
@ -195,6 +202,7 @@ func newUnstartedTunnel(stream client.ProxyService_ProxyClient, c clientConn) *g
conns: connectionManager{conns: make(map[int64]*conn)},
readTimeoutSeconds: 10,
done: make(chan struct{}),
started: 0,
}
s := metrics.ClientConnectionStatusCreated
t.prevStatus.Store(s)
@ -243,20 +251,17 @@ func (t *grpcTunnel) serve(tunnelCtx context.Context) {
}()
for {
pkt, err := t.stream.Recv()
pkt, err := t.Recv()
if err == io.EOF {
return
}
const segment = commonmetrics.SegmentToClient
isClosing := t.isClosing()
if err != nil || pkt == nil {
if !isClosing {
klog.ErrorS(err, "stream read failure")
}
metrics.Metrics.ObserveStreamErrorNoPacket(segment, err)
return
}
metrics.Metrics.ObservePacket(segment, pkt.Type)
if isClosing {
return
}
@ -335,11 +340,23 @@ func (t *grpcTunnel) serve(tunnelCtx context.Context) {
case client.PacketType_DATA:
resp := pkt.GetData()
if resp.ConnectID == 0 {
klog.ErrorS(nil, "Received packet missing ConnectID", "packetType", "DATA")
continue
}
// TODO: flow control
conn, ok := t.conns.get(resp.ConnectID)
if !ok {
klog.V(1).InfoS("Connection not recognized", "connectionID", resp.ConnectID)
klog.ErrorS(nil, "Connection not recognized", "connectionID", resp.ConnectID, "packetType", "DATA")
t.Send(&client.Packet{
Type: client.PacketType_CLOSE_REQ,
Payload: &client.Packet_CloseRequest{
CloseRequest: &client.CloseRequest{
ConnectID: resp.ConnectID,
},
},
})
continue
}
timer := time.NewTimer((time.Duration)(t.readTimeoutSeconds) * time.Second)
@ -358,7 +375,7 @@ func (t *grpcTunnel) serve(tunnelCtx context.Context) {
conn, ok := t.conns.get(resp.ConnectID)
if !ok {
klog.V(1).InfoS("Connection not recognized", "connectionID", resp.ConnectID)
klog.V(1).InfoS("Connection not recognized", "connectionID", resp.ConnectID, "packetType", "CLOSE_RSP")
continue
}
close(conn.readCh)
@ -382,6 +399,11 @@ func (t *grpcTunnel) DialContext(requestCtx context.Context, protocol, address s
}
func (t *grpcTunnel) dialContext(requestCtx context.Context, protocol, address string) (net.Conn, error) {
prevStarted := atomic.SwapUint32(&t.started, 1)
if prevStarted != 0 {
return nil, &dialFailure{"single-use dialer already dialed", metrics.DialFailureAlreadyStarted}
}
select {
case <-t.done:
return nil, errors.New("tunnel is closed")
@ -418,18 +440,15 @@ func (t *grpcTunnel) dialContext(requestCtx context.Context, protocol, address s
}
klog.V(5).InfoS("[tracing] send packet", "type", req.Type)
const segment = commonmetrics.SegmentFromClient
metrics.Metrics.ObservePacket(segment, req.Type)
err := t.stream.Send(req)
err := t.Send(req)
if err != nil {
metrics.Metrics.ObserveStreamError(segment, err, req.Type)
return nil, err
}
klog.V(5).Infoln("DIAL_REQ sent to proxy server")
c := &conn{
stream: t.stream,
tunnel: t,
random: random,
closeTunnel: t.closeTunnel,
}
@ -473,10 +492,7 @@ func (t *grpcTunnel) closeDial(dialID int64) {
},
},
}
const segment = commonmetrics.SegmentFromClient
metrics.Metrics.ObservePacket(segment, req.Type)
if err := t.stream.Send(req); err != nil {
metrics.Metrics.ObserveStreamError(segment, err, req.Type)
if err := t.Send(req); err != nil {
klog.V(5).InfoS("Failed to send DIAL_CLS", "err", err, "dialID", dialID)
}
t.closeTunnel()
@ -491,6 +507,35 @@ func (t *grpcTunnel) isClosing() bool {
return atomic.LoadUint32(&t.closing) != 0
}
func (t *grpcTunnel) Send(pkt *client.Packet) error {
t.sendLock.Lock()
defer t.sendLock.Unlock()
const segment = commonmetrics.SegmentFromClient
metrics.Metrics.ObservePacket(segment, pkt.Type)
err := t.stream.Send(pkt)
if err != nil && err != io.EOF {
metrics.Metrics.ObserveStreamError(segment, err, pkt.Type)
}
return err
}
func (t *grpcTunnel) Recv() (*client.Packet, error) {
t.recvLock.Lock()
defer t.recvLock.Unlock()
const segment = commonmetrics.SegmentToClient
pkt, err := t.stream.Recv()
if err != nil {
if err != io.EOF {
metrics.Metrics.ObserveStreamErrorNoPacket(segment, err)
}
return nil, err
}
metrics.Metrics.ObservePacket(segment, pkt.Type)
return pkt, nil
}
func GetDialFailureReason(err error) (isDialFailure bool, reason metrics.DialFailureReason) {
var df *dialFailure
if errors.As(err, &df) {

View File

@ -24,8 +24,6 @@ import (
"k8s.io/klog/v2"
"sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client/metrics"
commonmetrics "sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/common/metrics"
"sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client"
)
@ -38,7 +36,7 @@ var errConnCloseTimeout = errors.New("close timeout")
// conn is an implementation of net.Conn, where the data is transported
// over an established tunnel defined by a gRPC service ProxyService.
type conn struct {
stream client.ProxyService_ProxyClient
tunnel *grpcTunnel
connID int64
random int64
readCh chan []byte
@ -65,11 +63,8 @@ func (c *conn) Write(data []byte) (n int, err error) {
klog.V(5).InfoS("[tracing] send req", "type", req.Type)
const segment = commonmetrics.SegmentFromClient
metrics.Metrics.ObservePacket(segment, req.Type)
err = c.stream.Send(req)
err = c.tunnel.Send(req)
if err != nil {
metrics.Metrics.ObserveStreamError(segment, err, req.Type)
return 0, err
}
return len(data), err
@ -153,10 +148,7 @@ func (c *conn) Close() error {
klog.V(5).InfoS("[tracing] send req", "type", req.Type)
const segment = commonmetrics.SegmentFromClient
metrics.Metrics.ObservePacket(segment, req.Type)
if err := c.stream.Send(req); err != nil {
metrics.Metrics.ObserveStreamError(segment, err, req.Type)
if err := c.tunnel.Send(req); err != nil {
return err
}

View File

@ -62,6 +62,8 @@ const (
// DialFailureTunnelClosed indicates that the client connection was closed before the dial could
// complete.
DialFailureTunnelClosed DialFailureReason = "tunnelclosed"
// DialFailureAlreadyStarted indicates that a single-use tunnel dialer was already used once.
DialFailureAlreadyStarted DialFailureReason = "tunnelstarted"
)
type ClientConnectionStatus string

File diff suppressed because it is too large Load Diff

View File

@ -32,11 +32,6 @@ enum PacketType {
DIAL_CLS = 5;
}
enum Error {
EOF = 0;
// ...
}
message Packet {
PacketType type = 1;

View File

@ -0,0 +1,150 @@
/*
Copyright 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.
*/
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.12.4
// source: konnectivity-client/proto/client/client.proto
package client
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ProxyServiceClient is the client API for ProxyService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ProxyServiceClient interface {
Proxy(ctx context.Context, opts ...grpc.CallOption) (ProxyService_ProxyClient, error)
}
type proxyServiceClient struct {
cc grpc.ClientConnInterface
}
func NewProxyServiceClient(cc grpc.ClientConnInterface) ProxyServiceClient {
return &proxyServiceClient{cc}
}
func (c *proxyServiceClient) Proxy(ctx context.Context, opts ...grpc.CallOption) (ProxyService_ProxyClient, error) {
stream, err := c.cc.NewStream(ctx, &ProxyService_ServiceDesc.Streams[0], "/ProxyService/Proxy", opts...)
if err != nil {
return nil, err
}
x := &proxyServiceProxyClient{stream}
return x, nil
}
type ProxyService_ProxyClient interface {
Send(*Packet) error
Recv() (*Packet, error)
grpc.ClientStream
}
type proxyServiceProxyClient struct {
grpc.ClientStream
}
func (x *proxyServiceProxyClient) Send(m *Packet) error {
return x.ClientStream.SendMsg(m)
}
func (x *proxyServiceProxyClient) Recv() (*Packet, error) {
m := new(Packet)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// ProxyServiceServer is the server API for ProxyService service.
// All implementations should embed UnimplementedProxyServiceServer
// for forward compatibility
type ProxyServiceServer interface {
Proxy(ProxyService_ProxyServer) error
}
// UnimplementedProxyServiceServer should be embedded to have forward compatible implementations.
type UnimplementedProxyServiceServer struct {
}
func (UnimplementedProxyServiceServer) Proxy(ProxyService_ProxyServer) error {
return status.Errorf(codes.Unimplemented, "method Proxy not implemented")
}
// UnsafeProxyServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ProxyServiceServer will
// result in compilation errors.
type UnsafeProxyServiceServer interface {
mustEmbedUnimplementedProxyServiceServer()
}
func RegisterProxyServiceServer(s grpc.ServiceRegistrar, srv ProxyServiceServer) {
s.RegisterService(&ProxyService_ServiceDesc, srv)
}
func _ProxyService_Proxy_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(ProxyServiceServer).Proxy(&proxyServiceProxyServer{stream})
}
type ProxyService_ProxyServer interface {
Send(*Packet) error
Recv() (*Packet, error)
grpc.ServerStream
}
type proxyServiceProxyServer struct {
grpc.ServerStream
}
func (x *proxyServiceProxyServer) Send(m *Packet) error {
return x.ServerStream.SendMsg(m)
}
func (x *proxyServiceProxyServer) Recv() (*Packet, error) {
m := new(Packet)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// ProxyService_ServiceDesc is the grpc.ServiceDesc for ProxyService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ProxyService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ProxyService",
HandlerType: (*ProxyServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Proxy",
Handler: _ProxyService_Proxy_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "konnectivity-client/proto/client/client.proto",
}

View File

@ -75,6 +75,8 @@ import (
// either be any string type, an integer, implement json.Unmarshaler, or
// implement encoding.TextUnmarshaler.
//
// If the JSON-encoded data contain a syntax error, Unmarshal returns a SyntaxError.
//
// If a JSON value is not appropriate for a given target type,
// or if a JSON number overflows the target type, Unmarshal
// skips that field and completes the unmarshaling as best it can.
@ -85,14 +87,13 @@ import (
//
// The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
// not present, unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error.
//
// When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement
// character U+FFFD.
//
func Unmarshal(data []byte, v any, opts ...UnmarshalOpt) error {
// Check for well-formedness.
// Avoids filling out half a data structure

View File

@ -77,31 +77,31 @@ import (
//
// Examples of struct field tags and their meanings:
//
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
//
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
//
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
//
// // Field is ignored by this package.
// Field int `json:"-"`
// // Field is ignored by this package.
// Field int `json:"-"`
//
// // Field appears in JSON as key "-".
// Field int `json:"-,"`
// // Field appears in JSON as key "-".
// Field int `json:"-,"`
//
// The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs:
//
// Int64String int64 `json:",string"`
// Int64String int64 `json:",string"`
//
// The key name will be used if it's a non-empty string consisting of
// only Unicode letters, digits, and ASCII punctuation except quotation
@ -154,7 +154,6 @@ import (
// JSON cannot represent cyclic data structures and Marshal does not
// handle them. Passing cyclic structures to Marshal will result in
// an error.
//
func Marshal(v any) ([]byte, error) {
e := newEncodeState()
@ -784,7 +783,7 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
// We're a large number of nested ptrEncoder.encode calls deep;
// start checking if we've run into a pointer cycle.
ptr := v.Pointer()
ptr := v.UnsafePointer()
if _, ok := e.ptrSeen[ptr]; ok {
e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
}
@ -877,9 +876,9 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
// Here we use a struct to memorize the pointer to the first element of the slice
// and its length.
ptr := struct {
ptr uintptr
ptr interface{} // always an unsafe.Pointer, but avoids a dependency on package unsafe
len int
}{v.Pointer(), v.Len()}
}{v.UnsafePointer(), v.Len()}
if _, ok := e.ptrSeen[ptr]; ok {
e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
}

View File

@ -24,8 +24,9 @@ const (
// 4) simpleLetterEqualFold, no specials, no non-letters.
//
// The letters S and K are special because they map to 3 runes, not just 2:
// * S maps to s and to U+017F 'ſ' Latin small letter long s
// * k maps to K and to U+212A '' Kelvin sign
// - S maps to s and to U+017F 'ſ' Latin small letter long s
// - k maps to K and to U+212A '' Kelvin sign
//
// See https://play.golang.org/p/tTxjOc0OGo
//
// The returned function is specialized for matching against s and

View File

@ -27,6 +27,7 @@ func Valid(data []byte) bool {
// checkValid verifies that data is valid JSON-encoded data.
// scan is passed in for use by checkValid to avoid an allocation.
// checkValid returns nil or a SyntaxError.
func checkValid(data []byte, scan *scanner) error {
scan.reset()
for _, c := range data {
@ -42,6 +43,7 @@ func checkValid(data []byte, scan *scanner) error {
}
// A SyntaxError is a description of a JSON syntax error.
// Unmarshal will return a SyntaxError if the JSON can't be parsed.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes

View File

@ -289,7 +289,6 @@ var _ Unmarshaler = (*RawMessage)(nil)
// Number, for JSON numbers
// string, for JSON string literals
// nil, for JSON null
//
type Token any
*/

28
vendor/sigs.k8s.io/json/json.go generated vendored
View File

@ -34,13 +34,13 @@ type Decoder interface {
}
// NewDecoderCaseSensitivePreserveInts returns a decoder that matches the behavior of encoding/json#NewDecoder, with the following changes:
// - When unmarshaling into a struct, JSON keys must case-sensitively match `json` tag names (for tagged struct fields)
// or struct field names (for untagged struct fields), or they are treated as unknown fields and discarded.
// - When unmarshaling a number into an interface value, it is unmarshaled as an int64 if
// the JSON data does not contain a "." character and parses as an integer successfully and
// does not overflow int64. Otherwise, the number is unmarshaled as a float64.
// - If a syntax error is returned, it will not be of type encoding/json#SyntaxError,
// but will be recognizeable by this package's IsSyntaxError() function.
// - When unmarshaling into a struct, JSON keys must case-sensitively match `json` tag names (for tagged struct fields)
// or struct field names (for untagged struct fields), or they are treated as unknown fields and discarded.
// - When unmarshaling a number into an interface value, it is unmarshaled as an int64 if
// the JSON data does not contain a "." character and parses as an integer successfully and
// does not overflow int64. Otherwise, the number is unmarshaled as a float64.
// - If a syntax error is returned, it will not be of type encoding/json#SyntaxError,
// but will be recognizeable by this package's IsSyntaxError() function.
func NewDecoderCaseSensitivePreserveInts(r io.Reader) Decoder {
d := internaljson.NewDecoder(r)
d.CaseSensitive()
@ -51,13 +51,13 @@ func NewDecoderCaseSensitivePreserveInts(r io.Reader) Decoder {
// UnmarshalCaseSensitivePreserveInts parses the JSON-encoded data and stores the result in the value pointed to by v.
//
// UnmarshalCaseSensitivePreserveInts matches the behavior of encoding/json#Unmarshal, with the following changes:
// - When unmarshaling into a struct, JSON keys must case-sensitively match `json` tag names (for tagged struct fields)
// or struct field names (for untagged struct fields), or they are treated as unknown fields and discarded.
// - When unmarshaling a number into an interface value, it is unmarshaled as an int64 if
// the JSON data does not contain a "." character and parses as an integer successfully and
// does not overflow int64. Otherwise, the number is unmarshaled as a float64.
// - If a syntax error is returned, it will not be of type encoding/json#SyntaxError,
// but will be recognizeable by this package's IsSyntaxError() function.
// - When unmarshaling into a struct, JSON keys must case-sensitively match `json` tag names (for tagged struct fields)
// or struct field names (for untagged struct fields), or they are treated as unknown fields and discarded.
// - When unmarshaling a number into an interface value, it is unmarshaled as an int64 if
// the JSON data does not contain a "." character and parses as an integer successfully and
// does not overflow int64. Otherwise, the number is unmarshaled as a float64.
// - If a syntax error is returned, it will not be of type encoding/json#SyntaxError,
// but will be recognizeable by this package's IsSyntaxError() function.
func UnmarshalCaseSensitivePreserveInts(data []byte, v interface{}) error {
return internaljson.Unmarshal(
data,

View File

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

View File

@ -0,0 +1,356 @@
/*
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/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/typed"
)
// Converter is an interface to the conversion logic. The converter
// needs to be able to convert objects from one version to another.
type Converter interface {
Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error)
IsMissingVersionError(error) bool
}
// Updater is the object used to compute updated FieldSets and also
// merge the object on Apply.
type Updater struct {
Converter Converter
IgnoredFields map[fieldpath.APIVersion]*fieldpath.Set
enableUnions bool
}
// EnableUnionFeature turns on union handling. It is disabled by default until the
// feature is complete.
func (s *Updater) EnableUnionFeature() {
s.enableUnions = true
}
func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, *typed.Comparison, error) {
conflicts := fieldpath.ManagedFields{}
removed := fieldpath.ManagedFields{}
compare, err := oldObject.Compare(newObject)
if err != nil {
return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions := map[fieldpath.APIVersion]*typed.Comparison{
version: compare.ExcludeFields(s.IgnoredFields[version]),
}
for manager, managerSet := range managers {
if manager == workflow {
continue
}
compare, ok := versions[managerSet.APIVersion()]
if !ok {
var err error
versionedOldObject, err := s.Converter.Convert(oldObject, managerSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
delete(managers, manager)
continue
}
return nil, nil, fmt.Errorf("failed to convert old object: %v", err)
}
versionedNewObject, err := s.Converter.Convert(newObject, managerSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
delete(managers, manager)
continue
}
return nil, nil, fmt.Errorf("failed to convert new object: %v", err)
}
compare, err = versionedOldObject.Compare(versionedNewObject)
if err != nil {
return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions[managerSet.APIVersion()] = compare.ExcludeFields(s.IgnoredFields[managerSet.APIVersion()])
}
conflictSet := managerSet.Set().Intersection(compare.Modified.Union(compare.Added))
if !conflictSet.Empty() {
conflicts[manager] = fieldpath.NewVersionedSet(conflictSet, managerSet.APIVersion(), false)
}
if !compare.Removed.Empty() {
removed[manager] = fieldpath.NewVersionedSet(compare.Removed, managerSet.APIVersion(), false)
}
}
if !force && len(conflicts) != 0 {
return nil, nil, ConflictsFromManagers(conflicts)
}
for manager, conflictSet := range conflicts {
managers[manager] = fieldpath.NewVersionedSet(managers[manager].Set().Difference(conflictSet.Set()), managers[manager].APIVersion(), managers[manager].Applied())
}
for manager, removedSet := range removed {
managers[manager] = fieldpath.NewVersionedSet(managers[manager].Set().Difference(removedSet.Set()), managers[manager].APIVersion(), managers[manager].Applied())
}
for manager := range managers {
if managers[manager].Set().Empty() {
delete(managers, manager)
}
}
return managers, compare, nil
}
// Update is the method you should call once you've merged your final
// object on CREATE/UPDATE/PATCH verbs. newObject must be the object
// that you intend to persist (after applying the patch if this is for a
// PATCH call), and liveObject must be the original object (empty if
// this is a CREATE call).
func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (*typed.TypedValue, fieldpath.ManagedFields, error) {
var err error
managers, err = s.reconcileManagedFieldsWithSchemaChanges(liveObject, managers)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
if s.enableUnions {
newObject, err = liveObject.NormalizeUnions(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
managers, compare, err := s.update(liveObject, newObject, version, managers, manager, true)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
if _, ok := managers[manager]; !ok {
managers[manager] = fieldpath.NewVersionedSet(fieldpath.NewSet(), version, false)
}
ignored := s.IgnoredFields[version]
if ignored == nil {
ignored = fieldpath.NewSet()
}
managers[manager] = fieldpath.NewVersionedSet(
managers[manager].Set().Union(compare.Modified).Union(compare.Added).Difference(compare.Removed).RecursiveDifference(ignored),
version,
false,
)
if managers[manager].Set().Empty() {
delete(managers, manager)
}
return newObject, managers, nil
}
// Apply should be called when Apply is run, given the current object as
// well as the configuration that is applied. This will merge the object
// and return it. If the object hasn't changed, nil is returned (the
// managers can still have changed though).
func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (*typed.TypedValue, fieldpath.ManagedFields, error) {
var err error
managers, err = s.reconcileManagedFieldsWithSchemaChanges(liveObject, managers)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
if s.enableUnions {
configObject, err = configObject.NormalizeUnionsApply(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
newObject, err := liveObject.Merge(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
}
if s.enableUnions {
newObject, err = configObject.NormalizeUnionsApply(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
lastSet := managers[manager]
set, err := configObject.ToFieldSet()
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
}
ignored := s.IgnoredFields[version]
if ignored != nil {
set = set.RecursiveDifference(ignored)
// TODO: is this correct. If we don't remove from lastSet pruning might remove the fields?
if lastSet != nil {
lastSet.Set().RecursiveDifference(ignored)
}
}
managers[manager] = fieldpath.NewVersionedSet(set, version, true)
newObject, err = s.prune(newObject, managers, manager, lastSet)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to prune fields: %v", err)
}
managers, compare, err := s.update(liveObject, newObject, version, managers, manager, force)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
if compare.IsSame() {
newObject = nil
}
return newObject, managers, nil
}
// prune will remove a field, list or map item, iff:
// * applyingManager applied it last time
// * applyingManager didn't apply it this time
// * no other applier claims to manage it
func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet fieldpath.VersionedSet) (*typed.TypedValue, error) {
if lastSet == nil || lastSet.Set().Empty() {
return merged, nil
}
convertedMerged, err := s.Converter.Convert(merged, lastSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
return merged, nil
}
return nil, fmt.Errorf("failed to convert merged object to last applied version: %v", err)
}
sc, tr := convertedMerged.Schema(), convertedMerged.TypeRef()
pruned := convertedMerged.RemoveItems(lastSet.Set().EnsureNamedFieldsAreMembers(sc, tr))
pruned, err = s.addBackOwnedItems(convertedMerged, pruned, managers, applyingManager)
if err != nil {
return nil, fmt.Errorf("failed add back owned items: %v", err)
}
pruned, err = s.addBackDanglingItems(convertedMerged, pruned, lastSet)
if err != nil {
return nil, fmt.Errorf("failed add back dangling items: %v", err)
}
return s.Converter.Convert(pruned, managers[applyingManager].APIVersion())
}
// addBackOwnedItems adds back any fields, list and map items that were removed by prune,
// but other appliers or updaters (or the current applier's new config) claim to own.
func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (*typed.TypedValue, error) {
var err error
managedAtVersion := map[fieldpath.APIVersion]*fieldpath.Set{}
for _, managerSet := range managedFields {
if _, ok := managedAtVersion[managerSet.APIVersion()]; !ok {
managedAtVersion[managerSet.APIVersion()] = fieldpath.NewSet()
}
managedAtVersion[managerSet.APIVersion()] = managedAtVersion[managerSet.APIVersion()].Union(managerSet.Set())
}
// Add back owned items at pruned version first to avoid conversion failure
// caused by pruned fields which are required for conversion.
prunedVersion := fieldpath.APIVersion(*pruned.TypeRef().NamedType)
if managed, ok := managedAtVersion[prunedVersion]; ok {
merged, pruned, err = s.addBackOwnedItemsForVersion(merged, pruned, prunedVersion, managed)
if err != nil {
return nil, err
}
delete(managedAtVersion, prunedVersion)
}
for version, managed := range managedAtVersion {
merged, pruned, err = s.addBackOwnedItemsForVersion(merged, pruned, version, managed)
if err != nil {
return nil, err
}
}
return pruned, nil
}
// addBackOwnedItemsForVersion adds back any fields, list and map items that were removed by prune with specific managed field path at a version.
// It is an extracted sub-function from addBackOwnedItems for code reuse.
func (s *Updater) addBackOwnedItemsForVersion(merged, pruned *typed.TypedValue, version fieldpath.APIVersion, managed *fieldpath.Set) (*typed.TypedValue, *typed.TypedValue, error) {
var err error
merged, err = s.Converter.Convert(merged, version)
if err != nil {
if s.Converter.IsMissingVersionError(err) {
return merged, pruned, nil
}
return nil, nil, fmt.Errorf("failed to convert merged object at version %v: %v", version, err)
}
pruned, err = s.Converter.Convert(pruned, version)
if err != nil {
if s.Converter.IsMissingVersionError(err) {
return merged, pruned, nil
}
return nil, nil, fmt.Errorf("failed to convert pruned object at version %v: %v", version, err)
}
mergedSet, err := merged.ToFieldSet()
if err != nil {
return nil, nil, fmt.Errorf("failed to create field set from merged object at version %v: %v", version, err)
}
prunedSet, err := pruned.ToFieldSet()
if err != nil {
return nil, nil, fmt.Errorf("failed to create field set from pruned object at version %v: %v", version, err)
}
sc, tr := merged.Schema(), merged.TypeRef()
pruned = merged.RemoveItems(mergedSet.EnsureNamedFieldsAreMembers(sc, tr).Difference(prunedSet.EnsureNamedFieldsAreMembers(sc, tr).Union(managed.EnsureNamedFieldsAreMembers(sc, tr))))
return merged, pruned, nil
}
// addBackDanglingItems makes sure that the fields list and map items removed by prune were
// previously owned by the currently applying manager. This will add back fields list and map items
// that are unowned or that are owned by Updaters and shouldn't be removed.
func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet fieldpath.VersionedSet) (*typed.TypedValue, error) {
convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion())
if err != nil {
if s.Converter.IsMissingVersionError(err) {
return merged, nil
}
return nil, fmt.Errorf("failed to convert pruned object to last applied version: %v", err)
}
prunedSet, err := convertedPruned.ToFieldSet()
if err != nil {
return nil, fmt.Errorf("failed to create field set from pruned object in last applied version: %v", err)
}
mergedSet, err := merged.ToFieldSet()
if err != nil {
return nil, fmt.Errorf("failed to create field set from merged object in last applied version: %v", err)
}
sc, tr := merged.Schema(), merged.TypeRef()
prunedSet = prunedSet.EnsureNamedFieldsAreMembers(sc, tr)
mergedSet = mergedSet.EnsureNamedFieldsAreMembers(sc, tr)
last := lastSet.Set().EnsureNamedFieldsAreMembers(sc, tr)
return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(last)), nil
}
// reconcileManagedFieldsWithSchemaChanges reconciles the managed fields with any changes to the
// object's schema since the managed fields were written.
//
// Supports:
// - changing types from atomic to granular
// - changing types from granular to atomic
func (s *Updater) reconcileManagedFieldsWithSchemaChanges(liveObject *typed.TypedValue, managers fieldpath.ManagedFields) (fieldpath.ManagedFields, error) {
result := fieldpath.ManagedFields{}
for manager, versionedSet := range managers {
tv, err := s.Converter.Convert(liveObject, versionedSet.APIVersion())
if s.Converter.IsMissingVersionError(err) { // okay to skip, obsolete versions will be deleted automatically anyway
continue
}
if err != nil {
return nil, err
}
reconciled, err := typed.ReconcileFieldSetWithSchema(versionedSet.Set(), tv)
if err != nil {
return nil, err
}
if reconciled != nil {
result[manager] = fieldpath.NewVersionedSet(reconciled, versionedSet.APIVersion(), versionedSet.Applied())
} else {
result[manager] = versionedSet
}
}
return result, nil
}