fix(deps): update module k8s.io/apimachinery to v0.33.0 (main) (#17429)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
pull/17430/head
renovate[bot] 9 months ago committed by GitHub
parent 82acbd58b2
commit 0851b6fa08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      go.mod
  2. 19
      go.sum
  3. 180
      vendor/github.com/golang/protobuf/ptypes/any.go
  4. 10
      vendor/github.com/golang/protobuf/ptypes/doc.go
  5. 76
      vendor/github.com/golang/protobuf/ptypes/duration.go
  6. 112
      vendor/github.com/golang/protobuf/ptypes/timestamp.go
  7. 64
      vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go
  8. 8
      vendor/github.com/google/gnostic-models/compiler/extensions.go
  9. 96
      vendor/github.com/google/gnostic-models/extensions/extension.pb.go
  10. 6
      vendor/github.com/google/gnostic-models/extensions/extensions.go
  11. 1349
      vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.pb.go
  12. 1763
      vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.pb.go
  13. 182
      vendor/github.com/google/gnostic-models/openapiv3/annotations.pb.go
  14. 56
      vendor/github.com/google/gnostic-models/openapiv3/annotations.proto
  15. 10
      vendor/github.com/google/gofuzz/.travis.yml
  16. 67
      vendor/github.com/google/gofuzz/CONTRIBUTING.md
  17. 18
      vendor/github.com/google/gofuzz/doc.go
  18. 605
      vendor/github.com/google/gofuzz/fuzz.go
  19. 2
      vendor/k8s.io/apimachinery/pkg/api/errors/doc.go
  20. 2
      vendor/k8s.io/apimachinery/pkg/api/meta/doc.go
  21. 3
      vendor/k8s.io/apimachinery/pkg/api/meta/help.go
  22. 56
      vendor/k8s.io/apimachinery/pkg/api/operation/operation.go
  23. 2
      vendor/k8s.io/apimachinery/pkg/api/validation/doc.go
  24. 2
      vendor/k8s.io/apimachinery/pkg/api/validation/generic.go
  25. 2
      vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/doc.go
  26. 2
      vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go
  27. 2
      vendor/k8s.io/apimachinery/pkg/apis/meta/v1/doc.go
  28. 13
      vendor/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go
  29. 13
      vendor/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go
  30. 31
      vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go
  31. 4
      vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go
  32. 2
      vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go
  33. 2
      vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/doc.go
  34. 2
      vendor/k8s.io/apimachinery/pkg/conversion/doc.go
  35. 2
      vendor/k8s.io/apimachinery/pkg/conversion/queryparams/doc.go
  36. 2
      vendor/k8s.io/apimachinery/pkg/fields/doc.go
  37. 2
      vendor/k8s.io/apimachinery/pkg/labels/doc.go
  38. 2
      vendor/k8s.io/apimachinery/pkg/runtime/doc.go
  39. 1
      vendor/k8s.io/apimachinery/pkg/runtime/interfaces.go
  40. 39
      vendor/k8s.io/apimachinery/pkg/runtime/scheme.go
  41. 4
      vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/custom.go
  42. 23
      vendor/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go
  43. 230
      vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/collections.go
  44. 16
      vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go
  45. 174
      vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections.go
  46. 2
      vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/doc.go
  47. 87
      vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go
  48. 127
      vendor/k8s.io/apimachinery/pkg/runtime/types_proto.go
  49. 2
      vendor/k8s.io/apimachinery/pkg/types/doc.go
  50. 2
      vendor/k8s.io/apimachinery/pkg/util/diff/diff.go
  51. 2
      vendor/k8s.io/apimachinery/pkg/util/errors/doc.go
  52. 6
      vendor/k8s.io/apimachinery/pkg/util/framer/framer.go
  53. 14
      vendor/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go
  54. 46
      vendor/k8s.io/apimachinery/pkg/util/runtime/runtime.go
  55. 2
      vendor/k8s.io/apimachinery/pkg/util/sets/doc.go
  56. 212
      vendor/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go
  57. 132
      vendor/k8s.io/apimachinery/pkg/util/validation/field/errors.go
  58. 278
      vendor/k8s.io/apimachinery/pkg/util/validation/ip.go
  59. 40
      vendor/k8s.io/apimachinery/pkg/util/validation/validation.go
  60. 50
      vendor/k8s.io/apimachinery/pkg/util/wait/backoff.go
  61. 2
      vendor/k8s.io/apimachinery/pkg/util/wait/doc.go
  62. 4
      vendor/k8s.io/apimachinery/pkg/util/wait/loop.go
  63. 9
      vendor/k8s.io/apimachinery/pkg/util/wait/wait.go
  64. 163
      vendor/k8s.io/apimachinery/pkg/util/yaml/decoder.go
  65. 130
      vendor/k8s.io/apimachinery/pkg/util/yaml/stream_reader.go
  66. 4
      vendor/k8s.io/apimachinery/pkg/version/doc.go
  67. 28
      vendor/k8s.io/apimachinery/pkg/version/types.go
  68. 2
      vendor/k8s.io/apimachinery/pkg/watch/doc.go
  69. 15
      vendor/k8s.io/apimachinery/pkg/watch/streamwatcher.go
  70. 35
      vendor/k8s.io/apimachinery/pkg/watch/watch.go
  71. 2
      vendor/k8s.io/kube-openapi/pkg/handler3/handler.go
  72. 146
      vendor/k8s.io/kube-openapi/pkg/spec3/fuzz.go
  73. 25
      vendor/modules.txt
  74. 43
      vendor/sigs.k8s.io/randfill/CONTRIBUTING.md
  75. 6
      vendor/sigs.k8s.io/randfill/LICENSE
  76. 24
      vendor/sigs.k8s.io/randfill/NOTICE
  77. 8
      vendor/sigs.k8s.io/randfill/OWNERS
  78. 14
      vendor/sigs.k8s.io/randfill/OWNERS_ALIASES
  79. 45
      vendor/sigs.k8s.io/randfill/README.md
  80. 16
      vendor/sigs.k8s.io/randfill/SECURITY_CONTACTS
  81. 0
      vendor/sigs.k8s.io/randfill/bytesource/bytesource.go
  82. 3
      vendor/sigs.k8s.io/randfill/code-of-conduct.md
  83. 682
      vendor/sigs.k8s.io/randfill/randfill.go
  84. 50
      vendor/sigs.k8s.io/structured-merge-diff/v4/merge/update.go
  85. 47
      vendor/sigs.k8s.io/structured-merge-diff/v4/typed/typed.go
  86. 2
      vendor/sigs.k8s.io/structured-merge-diff/v4/value/scalar.go

@ -158,7 +158,7 @@ require (
golang.org/x/text v0.24.0
google.golang.org/protobuf v1.36.6
gotest.tools v2.2.0+incompatible
k8s.io/apimachinery v0.32.4
k8s.io/apimachinery v0.33.0
k8s.io/utils v0.0.0-20241210054802-24370beab758
zombiezen.com/go/sqlite v1.4.0
)
@ -248,6 +248,7 @@ require (
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.36.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
)
require (
@ -331,9 +332,8 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
@ -423,10 +423,10 @@ require (
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
k8s.io/api v0.32.3 // indirect
k8s.io/client-go v0.32.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
rsc.io/binaryregexp v0.2.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

@ -586,8 +586,8 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -1835,14 +1835,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
k8s.io/apimachinery v0.32.4 h1:8EEksaxA7nd7xWJkkwLDN4SvWS5ot9g6Z/VZb3ju25I=
k8s.io/apimachinery v0.32.4/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU=
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
@ -1875,8 +1875,11 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=

@ -1,180 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"fmt"
"strings"
"github.com/golang/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
anypb "github.com/golang/protobuf/ptypes/any"
)
const urlPrefix = "type.googleapis.com/"
// AnyMessageName returns the message name contained in an anypb.Any message.
// Most type assertions should use the Is function instead.
//
// Deprecated: Call the any.MessageName method instead.
func AnyMessageName(any *anypb.Any) (string, error) {
name, err := anyMessageName(any)
return string(name), err
}
func anyMessageName(any *anypb.Any) (protoreflect.FullName, error) {
if any == nil {
return "", fmt.Errorf("message is nil")
}
name := protoreflect.FullName(any.TypeUrl)
if i := strings.LastIndex(any.TypeUrl, "/"); i >= 0 {
name = name[i+len("/"):]
}
if !name.IsValid() {
return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl)
}
return name, nil
}
// MarshalAny marshals the given message m into an anypb.Any message.
//
// Deprecated: Call the anypb.New function instead.
func MarshalAny(m proto.Message) (*anypb.Any, error) {
switch dm := m.(type) {
case DynamicAny:
m = dm.Message
case *DynamicAny:
if dm == nil {
return nil, proto.ErrNil
}
m = dm.Message
}
b, err := proto.Marshal(m)
if err != nil {
return nil, err
}
return &anypb.Any{TypeUrl: urlPrefix + proto.MessageName(m), Value: b}, nil
}
// Empty returns a new message of the type specified in an anypb.Any message.
// It returns protoregistry.NotFound if the corresponding message type could not
// be resolved in the global registry.
//
// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead
// to resolve the message name and create a new instance of it.
func Empty(any *anypb.Any) (proto.Message, error) {
name, err := anyMessageName(any)
if err != nil {
return nil, err
}
mt, err := protoregistry.GlobalTypes.FindMessageByName(name)
if err != nil {
return nil, err
}
return proto.MessageV1(mt.New().Interface()), nil
}
// UnmarshalAny unmarshals the encoded value contained in the anypb.Any message
// into the provided message m. It returns an error if the target message
// does not match the type in the Any message or if an unmarshal error occurs.
//
// The target message m may be a *DynamicAny message. If the underlying message
// type could not be resolved, then this returns protoregistry.NotFound.
//
// Deprecated: Call the any.UnmarshalTo method instead.
func UnmarshalAny(any *anypb.Any, m proto.Message) error {
if dm, ok := m.(*DynamicAny); ok {
if dm.Message == nil {
var err error
dm.Message, err = Empty(any)
if err != nil {
return err
}
}
m = dm.Message
}
anyName, err := AnyMessageName(any)
if err != nil {
return err
}
msgName := proto.MessageName(m)
if anyName != msgName {
return fmt.Errorf("mismatched message type: got %q want %q", anyName, msgName)
}
return proto.Unmarshal(any.Value, m)
}
// Is reports whether the Any message contains a message of the specified type.
//
// Deprecated: Call the any.MessageIs method instead.
func Is(any *anypb.Any, m proto.Message) bool {
if any == nil || m == nil {
return false
}
name := proto.MessageName(m)
if !strings.HasSuffix(any.TypeUrl, name) {
return false
}
return len(any.TypeUrl) == len(name) || any.TypeUrl[len(any.TypeUrl)-len(name)-1] == '/'
}
// DynamicAny is a value that can be passed to UnmarshalAny to automatically
// allocate a proto.Message for the type specified in an anypb.Any message.
// The allocated message is stored in the embedded proto.Message.
//
// Example:
//
// var x ptypes.DynamicAny
// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... }
// fmt.Printf("unmarshaled message: %v", x.Message)
//
// Deprecated: Use the any.UnmarshalNew method instead to unmarshal
// the any message contents into a new instance of the underlying message.
type DynamicAny struct{ proto.Message }
func (m DynamicAny) String() string {
if m.Message == nil {
return "<nil>"
}
return m.Message.String()
}
func (m DynamicAny) Reset() {
if m.Message == nil {
return
}
m.Message.Reset()
}
func (m DynamicAny) ProtoMessage() {
return
}
func (m DynamicAny) ProtoReflect() protoreflect.Message {
if m.Message == nil {
return nil
}
return dynamicAny{proto.MessageReflect(m.Message)}
}
type dynamicAny struct{ protoreflect.Message }
func (m dynamicAny) Type() protoreflect.MessageType {
return dynamicAnyType{m.Message.Type()}
}
func (m dynamicAny) New() protoreflect.Message {
return dynamicAnyType{m.Message.Type()}.New()
}
func (m dynamicAny) Interface() protoreflect.ProtoMessage {
return DynamicAny{proto.MessageV1(m.Message.Interface())}
}
type dynamicAnyType struct{ protoreflect.MessageType }
func (t dynamicAnyType) New() protoreflect.Message {
return dynamicAny{t.MessageType.New()}
}
func (t dynamicAnyType) Zero() protoreflect.Message {
return dynamicAny{t.MessageType.Zero()}
}

@ -1,10 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ptypes provides functionality for interacting with well-known types.
//
// Deprecated: Well-known types have specialized functionality directly
// injected into the generated packages for each message type.
// See the deprecation notice for each function for the suggested alternative.
package ptypes

@ -1,76 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"errors"
"fmt"
"time"
durationpb "github.com/golang/protobuf/ptypes/duration"
)
// Range of google.protobuf.Duration as specified in duration.proto.
// This is about 10,000 years in seconds.
const (
maxSeconds = int64(10000 * 365.25 * 24 * 60 * 60)
minSeconds = -maxSeconds
)
// Duration converts a durationpb.Duration to a time.Duration.
// Duration returns an error if dur is invalid or overflows a time.Duration.
//
// Deprecated: Call the dur.AsDuration and dur.CheckValid methods instead.
func Duration(dur *durationpb.Duration) (time.Duration, error) {
if err := validateDuration(dur); err != nil {
return 0, err
}
d := time.Duration(dur.Seconds) * time.Second
if int64(d/time.Second) != dur.Seconds {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
}
if dur.Nanos != 0 {
d += time.Duration(dur.Nanos) * time.Nanosecond
if (d < 0) != (dur.Nanos < 0) {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
}
}
return d, nil
}
// DurationProto converts a time.Duration to a durationpb.Duration.
//
// Deprecated: Call the durationpb.New function instead.
func DurationProto(d time.Duration) *durationpb.Duration {
nanos := d.Nanoseconds()
secs := nanos / 1e9
nanos -= secs * 1e9
return &durationpb.Duration{
Seconds: int64(secs),
Nanos: int32(nanos),
}
}
// validateDuration determines whether the durationpb.Duration is valid
// according to the definition in google/protobuf/duration.proto.
// A valid durpb.Duration may still be too large to fit into a time.Duration
// Note that the range of durationpb.Duration is about 10,000 years,
// while the range of time.Duration is about 290 years.
func validateDuration(dur *durationpb.Duration) error {
if dur == nil {
return errors.New("duration: nil Duration")
}
if dur.Seconds < minSeconds || dur.Seconds > maxSeconds {
return fmt.Errorf("duration: %v: seconds out of range", dur)
}
if dur.Nanos <= -1e9 || dur.Nanos >= 1e9 {
return fmt.Errorf("duration: %v: nanos out of range", dur)
}
// Seconds and Nanos must have the same sign, unless d.Nanos is zero.
if (dur.Seconds < 0 && dur.Nanos > 0) || (dur.Seconds > 0 && dur.Nanos < 0) {
return fmt.Errorf("duration: %v: seconds and nanos have different signs", dur)
}
return nil
}

@ -1,112 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"errors"
"fmt"
"time"
timestamppb "github.com/golang/protobuf/ptypes/timestamp"
)
// Range of google.protobuf.Duration as specified in timestamp.proto.
const (
// Seconds field of the earliest valid Timestamp.
// This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
minValidSeconds = -62135596800
// Seconds field just after the latest valid Timestamp.
// This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
maxValidSeconds = 253402300800
)
// Timestamp converts a timestamppb.Timestamp to a time.Time.
// It returns an error if the argument is invalid.
//
// Unlike most Go functions, if Timestamp returns an error, the first return
// value is not the zero time.Time. Instead, it is the value obtained from the
// time.Unix function when passed the contents of the Timestamp, in the UTC
// locale. This may or may not be a meaningful time; many invalid Timestamps
// do map to valid time.Times.
//
// A nil Timestamp returns an error. The first return value in that case is
// undefined.
//
// Deprecated: Call the ts.AsTime and ts.CheckValid methods instead.
func Timestamp(ts *timestamppb.Timestamp) (time.Time, error) {
// Don't return the zero value on error, because corresponds to a valid
// timestamp. Instead return whatever time.Unix gives us.
var t time.Time
if ts == nil {
t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
} else {
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
}
return t, validateTimestamp(ts)
}
// TimestampNow returns a google.protobuf.Timestamp for the current time.
//
// Deprecated: Call the timestamppb.Now function instead.
func TimestampNow() *timestamppb.Timestamp {
ts, err := TimestampProto(time.Now())
if err != nil {
panic("ptypes: time.Now() out of Timestamp range")
}
return ts
}
// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
// It returns an error if the resulting Timestamp is invalid.
//
// Deprecated: Call the timestamppb.New function instead.
func TimestampProto(t time.Time) (*timestamppb.Timestamp, error) {
ts := &timestamppb.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()),
}
if err := validateTimestamp(ts); err != nil {
return nil, err
}
return ts, nil
}
// TimestampString returns the RFC 3339 string for valid Timestamps.
// For invalid Timestamps, it returns an error message in parentheses.
//
// Deprecated: Call the ts.AsTime method instead,
// followed by a call to the Format method on the time.Time value.
func TimestampString(ts *timestamppb.Timestamp) string {
t, err := Timestamp(ts)
if err != nil {
return fmt.Sprintf("(%v)", err)
}
return t.Format(time.RFC3339Nano)
}
// validateTimestamp determines whether a Timestamp is valid.
// A valid timestamp represents a time in the range [0001-01-01, 10000-01-01)
// and has a Nanos field in the range [0, 1e9).
//
// If the Timestamp is valid, validateTimestamp returns nil.
// Otherwise, it returns an error that describes the problem.
//
// Every valid Timestamp can be represented by a time.Time,
// but the converse is not true.
func validateTimestamp(ts *timestamppb.Timestamp) error {
if ts == nil {
return errors.New("timestamp: nil Timestamp")
}
if ts.Seconds < minValidSeconds {
return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
}
if ts.Seconds >= maxValidSeconds {
return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
}
if ts.Nanos < 0 || ts.Nanos >= 1e9 {
return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
}
return nil
}

@ -1,64 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: github.com/golang/protobuf/ptypes/timestamp/timestamp.proto
package timestamp
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
)
// Symbols defined in public import of google/protobuf/timestamp.proto.
type Timestamp = timestamppb.Timestamp
var File_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto protoreflect.FileDescriptor
var file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_rawDesc = []byte{
0x0a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x37,
0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x3b, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_goTypes = []interface{}{}
var file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_init() }
func file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_init() {
if File_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_goTypes,
DependencyIndexes: file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_depIdxs,
}.Build()
File_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto = out.File
file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_rawDesc = nil
file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_goTypes = nil
file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_depIdxs = nil
}

@ -20,8 +20,8 @@ import (
"os/exec"
"strings"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/any"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
yaml "gopkg.in/yaml.v3"
extensions "github.com/google/gnostic-models/extensions"
@ -33,7 +33,7 @@ type ExtensionHandler struct {
}
// CallExtension calls a binary extension handler.
func CallExtension(context *Context, in *yaml.Node, extensionName string) (handled bool, response *any.Any, err error) {
func CallExtension(context *Context, in *yaml.Node, extensionName string) (handled bool, response *anypb.Any, err error) {
if context == nil || context.ExtensionHandlers == nil {
return false, nil, nil
}
@ -50,7 +50,7 @@ func CallExtension(context *Context, in *yaml.Node, extensionName string) (handl
return handled, response, err
}
func (extensionHandlers *ExtensionHandler) handle(in *yaml.Node, extensionName string) (*any.Any, error) {
func (extensionHandlers *ExtensionHandler) handle(in *yaml.Node, extensionName string) (*anypb.Any, error) {
if extensionHandlers.Name != "" {
yamlData, _ := yaml.Marshal(in)
request := &extensions.ExtensionHandlerRequest{

@ -14,8 +14,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.3
// protoc-gen-go v1.35.1
// protoc v4.23.4
// source: extensions/extension.proto
package gnostic_extension_v1
@ -51,11 +51,9 @@ type Version struct {
func (x *Version) Reset() {
*x = Version{}
if protoimpl.UnsafeEnabled {
mi := &file_extensions_extension_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_extensions_extension_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Version) String() string {
@ -66,7 +64,7 @@ func (*Version) ProtoMessage() {}
func (x *Version) ProtoReflect() protoreflect.Message {
mi := &file_extensions_extension_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -123,11 +121,9 @@ type ExtensionHandlerRequest struct {
func (x *ExtensionHandlerRequest) Reset() {
*x = ExtensionHandlerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_extensions_extension_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_extensions_extension_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ExtensionHandlerRequest) String() string {
@ -138,7 +134,7 @@ func (*ExtensionHandlerRequest) ProtoMessage() {}
func (x *ExtensionHandlerRequest) ProtoReflect() protoreflect.Message {
mi := &file_extensions_extension_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -191,11 +187,9 @@ type ExtensionHandlerResponse struct {
func (x *ExtensionHandlerResponse) Reset() {
*x = ExtensionHandlerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_extensions_extension_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_extensions_extension_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ExtensionHandlerResponse) String() string {
@ -206,7 +200,7 @@ func (*ExtensionHandlerResponse) ProtoMessage() {}
func (x *ExtensionHandlerResponse) ProtoReflect() protoreflect.Message {
mi := &file_extensions_extension_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -257,11 +251,9 @@ type Wrapper struct {
func (x *Wrapper) Reset() {
*x = Wrapper{}
if protoimpl.UnsafeEnabled {
mi := &file_extensions_extension_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_extensions_extension_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Wrapper) String() string {
@ -272,7 +264,7 @@ func (*Wrapper) ProtoMessage() {}
func (x *Wrapper) ProtoReflect() protoreflect.Message {
mi := &file_extensions_extension_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -367,7 +359,7 @@ func file_extensions_extension_proto_rawDescGZIP() []byte {
}
var file_extensions_extension_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_extensions_extension_proto_goTypes = []interface{}{
var file_extensions_extension_proto_goTypes = []any{
(*Version)(nil), // 0: gnostic.extension.v1.Version
(*ExtensionHandlerRequest)(nil), // 1: gnostic.extension.v1.ExtensionHandlerRequest
(*ExtensionHandlerResponse)(nil), // 2: gnostic.extension.v1.ExtensionHandlerResponse
@ -390,56 +382,6 @@ func file_extensions_extension_proto_init() {
if File_extensions_extension_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_extensions_extension_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Version); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_extensions_extension_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ExtensionHandlerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_extensions_extension_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ExtensionHandlerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_extensions_extension_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Wrapper); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{

@ -19,8 +19,8 @@ import (
"log"
"os"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)
type extensionHandler func(name string, yamlInput string) (bool, proto.Message, error)
@ -54,7 +54,7 @@ func Main(handler extensionHandler) {
response.Errors = append(response.Errors, err.Error())
} else if handled {
response.Handled = true
response.Value, err = ptypes.MarshalAny(output)
response.Value, err = anypb.New(output)
if err != nil {
response.Errors = append(response.Errors, err.Error())
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,182 @@
// Copyright 2022 Google LLC. All Rights Reserved.
//
// 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. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc v4.23.4
// source: openapiv3/annotations.proto
package openapi_v3
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
reflect "reflect"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var file_openapiv3_annotations_proto_extTypes = []protoimpl.ExtensionInfo{
{
ExtendedType: (*descriptorpb.FileOptions)(nil),
ExtensionType: (*Document)(nil),
Field: 1143,
Name: "openapi.v3.document",
Tag: "bytes,1143,opt,name=document",
Filename: "openapiv3/annotations.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*Operation)(nil),
Field: 1143,
Name: "openapi.v3.operation",
Tag: "bytes,1143,opt,name=operation",
Filename: "openapiv3/annotations.proto",
},
{
ExtendedType: (*descriptorpb.MessageOptions)(nil),
ExtensionType: (*Schema)(nil),
Field: 1143,
Name: "openapi.v3.schema",
Tag: "bytes,1143,opt,name=schema",
Filename: "openapiv3/annotations.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*Schema)(nil),
Field: 1143,
Name: "openapi.v3.property",
Tag: "bytes,1143,opt,name=property",
Filename: "openapiv3/annotations.proto",
},
}
// Extension fields to descriptorpb.FileOptions.
var (
// optional openapi.v3.Document document = 1143;
E_Document = &file_openapiv3_annotations_proto_extTypes[0]
)
// Extension fields to descriptorpb.MethodOptions.
var (
// optional openapi.v3.Operation operation = 1143;
E_Operation = &file_openapiv3_annotations_proto_extTypes[1]
)
// Extension fields to descriptorpb.MessageOptions.
var (
// optional openapi.v3.Schema schema = 1143;
E_Schema = &file_openapiv3_annotations_proto_extTypes[2]
)
// Extension fields to descriptorpb.FieldOptions.
var (
// optional openapi.v3.Schema property = 1143;
E_Property = &file_openapiv3_annotations_proto_extTypes[3]
)
var File_openapiv3_annotations_proto protoreflect.FileDescriptor
var file_openapiv3_annotations_proto_rawDesc = []byte{
0x0a, 0x1b, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x33, 0x2f, 0x61, 0x6e, 0x6e, 0x6f,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x6f,
0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x6f, 0x70, 0x65,
0x6e, 0x61, 0x70, 0x69, 0x76, 0x33, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x76, 0x33,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3a, 0x4f, 0x0a, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65,
0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70,
0x69, 0x2e, 0x76, 0x33, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x64,
0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x54, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70,
0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x4c, 0x0a,
0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x63, 0x68,
0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x3a, 0x4e, 0x0a, 0x08, 0x70,
0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d,
0x61, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x42, 0x42, 0x0a, 0x0e, 0x6f,
0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x33, 0x42, 0x10, 0x41,
0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50,
0x01, 0x5a, 0x16, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x33, 0x3b, 0x6f,
0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x33, 0xa2, 0x02, 0x03, 0x4f, 0x41, 0x53, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var file_openapiv3_annotations_proto_goTypes = []any{
(*descriptorpb.FileOptions)(nil), // 0: google.protobuf.FileOptions
(*descriptorpb.MethodOptions)(nil), // 1: google.protobuf.MethodOptions
(*descriptorpb.MessageOptions)(nil), // 2: google.protobuf.MessageOptions
(*descriptorpb.FieldOptions)(nil), // 3: google.protobuf.FieldOptions
(*Document)(nil), // 4: openapi.v3.Document
(*Operation)(nil), // 5: openapi.v3.Operation
(*Schema)(nil), // 6: openapi.v3.Schema
}
var file_openapiv3_annotations_proto_depIdxs = []int32{
0, // 0: openapi.v3.document:extendee -> google.protobuf.FileOptions
1, // 1: openapi.v3.operation:extendee -> google.protobuf.MethodOptions
2, // 2: openapi.v3.schema:extendee -> google.protobuf.MessageOptions
3, // 3: openapi.v3.property:extendee -> google.protobuf.FieldOptions
4, // 4: openapi.v3.document:type_name -> openapi.v3.Document
5, // 5: openapi.v3.operation:type_name -> openapi.v3.Operation
6, // 6: openapi.v3.schema:type_name -> openapi.v3.Schema
6, // 7: openapi.v3.property:type_name -> openapi.v3.Schema
8, // [8:8] is the sub-list for method output_type
8, // [8:8] is the sub-list for method input_type
4, // [4:8] is the sub-list for extension type_name
0, // [0:4] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_openapiv3_annotations_proto_init() }
func file_openapiv3_annotations_proto_init() {
if File_openapiv3_annotations_proto != nil {
return
}
file_openapiv3_OpenAPIv3_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_openapiv3_annotations_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 4,
NumServices: 0,
},
GoTypes: file_openapiv3_annotations_proto_goTypes,
DependencyIndexes: file_openapiv3_annotations_proto_depIdxs,
ExtensionInfos: file_openapiv3_annotations_proto_extTypes,
}.Build()
File_openapiv3_annotations_proto = out.File
file_openapiv3_annotations_proto_rawDesc = nil
file_openapiv3_annotations_proto_goTypes = nil
file_openapiv3_annotations_proto_depIdxs = nil
}

@ -0,0 +1,56 @@
// Copyright 2022 Google LLC. All Rights Reserved.
//
// 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.
syntax = "proto3";
package openapi.v3;
import "google/protobuf/descriptor.proto";
import "openapiv3/OpenAPIv3.proto";
// The Go package name.
option go_package = "./openapiv3;openapi_v3";
// This option lets the proto compiler generate Java code inside the package
// name (see below) instead of inside an outer class. It creates a simpler
// developer experience by reducing one-level of name nesting and be
// consistent with most programming languages that don't support outer classes.
option java_multiple_files = true;
// The Java outer classname should be the filename in UpperCamelCase. This
// class is only used to hold proto descriptor, so developers don't need to
// work with it directly.
option java_outer_classname = "AnnotationsProto";
// The Java package name must be proto package name with proper prefix.
option java_package = "org.openapi_v3";
// A reasonable prefix for the Objective-C symbols generated from the package.
// It should at a minimum be 3 characters long, all uppercase, and convention
// is to use an abbreviation of the package name. Something short, but
// hopefully unique enough to not conflict with things that may come along in
// the future. 'GPB' is reserved for the protocol buffer implementation itself.
option objc_class_prefix = "OAS";
extend google.protobuf.FileOptions {
Document document = 1143;
}
extend google.protobuf.MethodOptions {
Operation operation = 1143;
}
extend google.protobuf.MessageOptions {
Schema schema = 1143;
}
extend google.protobuf.FieldOptions {
Schema property = 1143;
}

@ -1,10 +0,0 @@
language: go
go:
- 1.11.x
- 1.12.x
- 1.13.x
- master
script:
- go test -cover

@ -1,67 +0,0 @@
# How to contribute #
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement ##
Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright **assignment**, it simply gives
Google permission to use and redistribute your contributions as part of the
project.
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual
CLA][].
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA][].
You generally only need to submit a CLA once, so if you've already submitted
one (even if it was for a different project), you probably don't need to do it
again.
[individual CLA]: https://developers.google.com/open-source/cla/individual
[corporate CLA]: https://developers.google.com/open-source/cla/corporate
## Submitting a patch ##
1. It's generally best to start by opening a new issue describing the bug or
feature you're intending to fix. Even if you think it's relatively minor,
it's helpful to know what people are working on. Mention in the initial
issue that you are planning to work on that bug or feature so that it can
be assigned to you.
1. Follow the normal process of [forking][] the project, and setup a new
branch to work in. It's important that each group of changes be done in
separate branches in order to ensure that a pull request only includes the
commits related to that bug or feature.
1. Go makes it very simple to ensure properly formatted code, so always run
`go fmt` on your code before committing it. You should also run
[golint][] over your code. As noted in the [golint readme][], it's not
strictly necessary that your code be completely "lint-free", but this will
help you find common style issues.
1. Any significant changes should almost always be accompanied by tests. The
project already has good test coverage, so look at some of the existing
tests if you're unsure how to go about it. [gocov][] and [gocov-html][]
are invaluable tools for seeing which parts of your code aren't being
exercised by your tests.
1. Do your best to have [well-formed commit messages][] for each change.
This provides consistency throughout the project, and ensures that commit
messages are able to be formatted properly by various git tools.
1. Finally, push the commits to your fork and submit a [pull request][].
[forking]: https://help.github.com/articles/fork-a-repo
[golint]: https://github.com/golang/lint
[golint readme]: https://github.com/golang/lint/blob/master/README
[gocov]: https://github.com/axw/gocov
[gocov-html]: https://github.com/matm/gocov-html
[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
[pull request]: https://help.github.com/articles/creating-a-pull-request

@ -1,18 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 fuzz is a library for populating go objects with random values.
package fuzz

@ -1,605 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 fuzz
import (
"fmt"
"math/rand"
"reflect"
"regexp"
"time"
"github.com/google/gofuzz/bytesource"
"strings"
)
// fuzzFuncMap is a map from a type to a fuzzFunc that handles that type.
type fuzzFuncMap map[reflect.Type]reflect.Value
// Fuzzer knows how to fill any object with random fields.
type Fuzzer struct {
fuzzFuncs fuzzFuncMap
defaultFuzzFuncs fuzzFuncMap
r *rand.Rand
nilChance float64
minElements int
maxElements int
maxDepth int
skipFieldPatterns []*regexp.Regexp
}
// 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(seed)),
nilChance: .2,
minElements: 1,
maxElements: 10,
maxDepth: 100,
}
return f
}
// NewFromGoFuzz is a helper function that enables using gofuzz (this
// project) with go-fuzz (https://github.com/dvyukov/go-fuzz) for continuous
// fuzzing. Essentially, it enables translating the fuzzing bytes from
// go-fuzz to any Go object using this library.
//
// This implementation promises a constant translation from a given slice of
// bytes to the fuzzed objects. This promise will remain over future
// versions of Go and of this library.
//
// Note: the returned Fuzzer should not be shared between multiple goroutines,
// as its deterministic output will no longer be available.
//
// Example: use go-fuzz to test the function `MyFunc(int)` in the package
// `mypackage`. Add the file: "mypacakge_fuzz.go" with the content:
//
// // +build gofuzz
// package mypacakge
// import fuzz "github.com/google/gofuzz"
// func Fuzz(data []byte) int {
// var i int
// fuzz.NewFromGoFuzz(data).Fuzz(&i)
// MyFunc(i)
// return 0
// }
func NewFromGoFuzz(data []byte) *Fuzzer {
return New().RandSource(bytesource.New(data))
}
// Funcs adds each entry in fuzzFuncs as a custom fuzzing function.
//
// Each entry in fuzzFuncs must be a function taking two parameters.
// The first parameter must be a pointer or map. It is the variable that
// function will fill with random data. The second parameter must be a
// fuzz.Continue, which will provide a source of randomness and a way
// to automatically continue fuzzing smaller pieces of the first parameter.
//
// These functions are called sensibly, e.g., if you wanted custom string
// fuzzing, the function `func(s *string, c fuzz.Continue)` would get
// called and passed the address of strings. Maps and pointers will always
// be made/new'd for you, ignoring the NilChange option. For slices, it
// doesn't make much sense to pre-create them--Fuzzer doesn't know how
// long you want your slice--so take a pointer to a slice, and make it
// yourself. (If you don't want your map/pointer type pre-made, take a
// pointer to it, and make it yourself.) See the examples for a range of
// custom functions.
func (f *Fuzzer) Funcs(fuzzFuncs ...interface{}) *Fuzzer {
for i := range fuzzFuncs {
v := reflect.ValueOf(fuzzFuncs[i])
if v.Kind() != reflect.Func {
panic("Need only funcs!")
}
t := v.Type()
if t.NumIn() != 2 || t.NumOut() != 0 {
panic("Need 2 in and 0 out params!")
}
argT := t.In(0)
switch argT.Kind() {
case reflect.Ptr, reflect.Map:
default:
panic("fuzzFunc must take pointer or map type")
}
if t.In(1) != reflect.TypeOf(Continue{}) {
panic("fuzzFunc's second parameter must be type fuzz.Continue")
}
f.fuzzFuncs[argT] = v
}
return f
}
// RandSource causes f to get values from the given source of randomness.
// Use if you want deterministic fuzzing.
func (f *Fuzzer) RandSource(s rand.Source) *Fuzzer {
f.r = rand.New(s)
return f
}
// NilChance sets the probability of creating a nil pointer, map, or slice to
// 'p'. 'p' should be between 0 (no nils) and 1 (all nils), inclusive.
func (f *Fuzzer) NilChance(p float64) *Fuzzer {
if p < 0 || p > 1 {
panic("p should be between 0 and 1, inclusive.")
}
f.nilChance = p
return f
}
// NumElements sets the minimum and maximum number of elements that will be
// added to a non-nil map or slice.
func (f *Fuzzer) NumElements(atLeast, atMost int) *Fuzzer {
if atLeast > atMost {
panic("atLeast must be <= atMost")
}
if atLeast < 0 {
panic("atLeast must be >= 0")
}
f.minElements = atLeast
f.maxElements = atMost
return f
}
func (f *Fuzzer) genElementCount() int {
if f.minElements == f.maxElements {
return f.minElements
}
return f.minElements + f.r.Intn(f.maxElements-f.minElements+1)
}
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
}
// Skip fields which match the supplied pattern. Call this multiple times if needed
// This is useful to skip XXX_ fields generated by protobuf
func (f *Fuzzer) SkipFieldsWithPattern(pattern *regexp.Regexp) *Fuzzer {
f.skipFieldPatterns = append(f.skipFieldPatterns, pattern)
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,
// if so, calls Fuzz on it to fuzz itself. If that fails, this will see if
// there is a default fuzz function provided by this package. If all of that
// fails, this will generate random values for all primitive fields and then
// recurse for all non-primitives.
//
// 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.
func (f *Fuzzer) Fuzz(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
f.fuzzWithContext(v, 0)
}
// FuzzNoCustom is just like Fuzz, except that any custom fuzz function for
// obj's type will not be called and obj will not be tested for fuzz.Interface
// conformance. This applies only to obj and not other instances of obj's
// type.
// Not safe for cyclic or tree-like structs!
// 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) FuzzNoCustom(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
f.fuzzWithContext(v, flagNoCustomFuzz)
}
const (
// Do not try to find a custom fuzz function. Does not apply recursively.
flagNoCustomFuzz uint64 = 1 << iota
)
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() && fc.tryCustom(v.Addr()) {
return
}
if fc.tryCustom(v) {
return
}
}
if fn, ok := fillFuncMap[v.Kind()]; ok {
fn(v, fc.fuzzer.r)
return
}
switch v.Kind() {
case reflect.Map:
if fc.fuzzer.genShouldFill() {
v.Set(reflect.MakeMap(v.Type()))
n := fc.fuzzer.genElementCount()
for i := 0; i < n; i++ {
key := reflect.New(v.Type().Key()).Elem()
fc.doFuzz(key, 0)
val := reflect.New(v.Type().Elem()).Elem()
fc.doFuzz(val, 0)
v.SetMapIndex(key, val)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Ptr:
if fc.fuzzer.genShouldFill() {
v.Set(reflect.New(v.Type().Elem()))
fc.doFuzz(v.Elem(), 0)
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Slice:
if fc.fuzzer.genShouldFill() {
n := fc.fuzzer.genElementCount()
v.Set(reflect.MakeSlice(v.Type(), n, n))
for i := 0; i < n; i++ {
fc.doFuzz(v.Index(i), 0)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Array:
if fc.fuzzer.genShouldFill() {
n := v.Len()
for i := 0; i < n; i++ {
fc.doFuzz(v.Index(i), 0)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
skipField := false
fieldName := v.Type().Field(i).Name
for _, pattern := range fc.fuzzer.skipFieldPatterns {
if pattern.MatchString(fieldName) {
skipField = true
break
}
}
if !skipField {
fc.doFuzz(v.Field(i), 0)
}
}
case reflect.Chan:
fallthrough
case reflect.Func:
fallthrough
case reflect.Interface:
fallthrough
default:
panic(fmt.Sprintf("Can't handle %#v", v.Interface()))
}
}
// tryCustom searches for custom handlers, and returns true iff it finds a match
// and successfully randomizes v.
func (fc *fuzzerContext) tryCustom(v reflect.Value) bool {
// First: see if we have a fuzz function for it.
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{fc: fc, Rand: fc.fuzzer.r})
return true
}
}
// Finally: see if there is a default fuzz function.
doCustom, ok = fc.fuzzer.defaultFuzzFuncs[v.Type()]
if !ok {
return false
}
}
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.New(v.Type().Elem()))
}
case reflect.Map:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.MakeMap(v.Type()))
}
default:
return false
}
doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{
fc: fc,
Rand: fc.fuzzer.r,
})})
return true
}
// Interface represents an object that knows how to fuzz itself. Any time we
// find a type that implements this interface we will delegate the act of
// fuzzing itself.
type Interface interface {
Fuzz(c Continue)
}
// 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 {
fc *fuzzerContext
// For convenience, Continue implements rand.Rand via embedding.
// Use this for generating any randomness if you want your fuzzing
// to be repeatable for a given seed.
*rand.Rand
}
// Fuzz continues fuzzing obj. obj must be a pointer.
func (c Continue) Fuzz(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
c.fc.doFuzz(v, 0)
}
// FuzzNoCustom continues fuzzing obj, except that any custom fuzz function for
// obj's type will not be called and obj will not be tested for fuzz.Interface
// conformance. This applies only to obj and not other instances of obj's
// type.
func (c Continue) FuzzNoCustom(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
c.fc.doFuzz(v, flagNoCustomFuzz)
}
// RandString makes a random string up to 20 characters long. The returned string
// may include a variety of (valid) UTF-8 encodings.
func (c Continue) RandString() string {
return randString(c.Rand)
}
// RandUint64 makes random 64 bit numbers.
// Weirdly, rand doesn't have a function that gives you 64 random bits.
func (c Continue) RandUint64() uint64 {
return randUint64(c.Rand)
}
// RandBool returns true or false randomly.
func (c Continue) RandBool() bool {
return randBool(c.Rand)
}
func fuzzInt(v reflect.Value, r *rand.Rand) {
v.SetInt(int64(randUint64(r)))
}
func fuzzUint(v reflect.Value, r *rand.Rand) {
v.SetUint(randUint64(r))
}
func fuzzTime(t *time.Time, c Continue) {
var sec, nsec int64
// Allow for about 1000 years of random time values, which keeps things
// like JSON parsing reasonably happy.
sec = c.Rand.Int63n(1000 * 365 * 24 * 60 * 60)
c.Fuzz(&nsec)
*t = time.Unix(sec, nsec)
}
var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){
reflect.Bool: func(v reflect.Value, r *rand.Rand) {
v.SetBool(randBool(r))
},
reflect.Int: fuzzInt,
reflect.Int8: fuzzInt,
reflect.Int16: fuzzInt,
reflect.Int32: fuzzInt,
reflect.Int64: fuzzInt,
reflect.Uint: fuzzUint,
reflect.Uint8: fuzzUint,
reflect.Uint16: fuzzUint,
reflect.Uint32: fuzzUint,
reflect.Uint64: fuzzUint,
reflect.Uintptr: fuzzUint,
reflect.Float32: func(v reflect.Value, r *rand.Rand) {
v.SetFloat(float64(r.Float32()))
},
reflect.Float64: func(v reflect.Value, r *rand.Rand) {
v.SetFloat(r.Float64())
},
reflect.Complex64: func(v reflect.Value, r *rand.Rand) {
v.SetComplex(complex128(complex(r.Float32(), r.Float32())))
},
reflect.Complex128: func(v reflect.Value, r *rand.Rand) {
v.SetComplex(complex(r.Float64(), r.Float64()))
},
reflect.String: func(v reflect.Value, r *rand.Rand) {
v.SetString(randString(r))
},
reflect.UnsafePointer: func(v reflect.Value, r *rand.Rand) {
panic("unimplemented")
},
}
// randBool returns true or false randomly.
func randBool(r *rand.Rand) bool {
return r.Int31()&(1<<30) == 0
}
type int63nPicker interface {
Int63n(int64) int64
}
// UnicodeRange describes a sequential range of unicode characters.
// Last must be numerically greater than First.
type UnicodeRange struct {
First, Last rune
}
// UnicodeRanges describes an arbitrary number of sequential ranges of unicode characters.
// To be useful, each range must have at least one character (First <= Last) and
// there must be at least one range.
type UnicodeRanges []UnicodeRange
// choose returns a random unicode character from the given range, using the
// given randomness source.
func (ur UnicodeRange) choose(r int63nPicker) rune {
count := int64(ur.Last - ur.First + 1)
return ur.First + rune(r.Int63n(count))
}
// CustomStringFuzzFunc constructs a FuzzFunc which produces random strings.
// Each character is selected from the range ur. If there are no characters
// in the range (cr.Last < cr.First), this will panic.
func (ur UnicodeRange) CustomStringFuzzFunc() func(s *string, c Continue) {
ur.check()
return func(s *string, c Continue) {
*s = ur.randString(c.Rand)
}
}
// check is a function that used to check whether the first of ur(UnicodeRange)
// is greater than the last one.
func (ur UnicodeRange) check() {
if ur.Last < ur.First {
panic("The last encoding must be greater than the first one.")
}
}
// randString of UnicodeRange makes a random string up to 20 characters long.
// Each character is selected form ur(UnicodeRange).
func (ur UnicodeRange) randString(r *rand.Rand) string {
n := r.Intn(20)
sb := strings.Builder{}
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteRune(ur.choose(r))
}
return sb.String()
}
// defaultUnicodeRanges sets a default unicode range when user do not set
// CustomStringFuzzFunc() but wants fuzz string.
var defaultUnicodeRanges = UnicodeRanges{
{' ', '~'}, // ASCII characters
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
}
// CustomStringFuzzFunc constructs a FuzzFunc which produces random strings.
// Each character is selected from one of the ranges of ur(UnicodeRanges).
// Each range has an equal probability of being chosen. If there are no ranges,
// or a selected range has no characters (.Last < .First), this will panic.
// Do not modify any of the ranges in ur after calling this function.
func (ur UnicodeRanges) CustomStringFuzzFunc() func(s *string, c Continue) {
// Check unicode ranges slice is empty.
if len(ur) == 0 {
panic("UnicodeRanges is empty.")
}
// if not empty, each range should be checked.
for i := range ur {
ur[i].check()
}
return func(s *string, c Continue) {
*s = ur.randString(c.Rand)
}
}
// randString of UnicodeRanges makes a random string up to 20 characters long.
// Each character is selected form one of the ranges of ur(UnicodeRanges),
// and each range has an equal probability of being chosen.
func (ur UnicodeRanges) randString(r *rand.Rand) string {
n := r.Intn(20)
sb := strings.Builder{}
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteRune(ur[r.Intn(len(ur))].choose(r))
}
return sb.String()
}
// randString makes a random string up to 20 characters long. The returned string
// may include a variety of (valid) UTF-8 encodings.
func randString(r *rand.Rand) string {
return defaultUnicodeRanges.randString(r)
}
// randUint64 makes random 64 bit numbers.
// Weirdly, rand doesn't have a function that gives you 64 random bits.
func randUint64(r *rand.Rand) uint64 {
return uint64(r.Uint32())<<32 | uint64(r.Uint32())
}

@ -15,4 +15,4 @@ limitations under the License.
*/
// Package errors provides detailed error types for api field validation.
package errors // import "k8s.io/apimachinery/pkg/api/errors"
package errors

@ -16,4 +16,4 @@ limitations under the License.
// Package meta provides functions for retrieving API metadata from objects
// belonging to the Kubernetes API
package meta // import "k8s.io/apimachinery/pkg/api/meta"
package meta

@ -221,6 +221,9 @@ func extractList(obj runtime.Object, allocNew bool) ([]runtime.Object, error) {
if err != nil {
return nil, err
}
if items.IsNil() {
return nil, nil
}
list := make([]runtime.Object, items.Len())
if len(list) == 0 {
return list, nil

@ -0,0 +1,56 @@
/*
Copyright 2024 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 operation
import "k8s.io/apimachinery/pkg/util/sets"
// Operation provides contextual information about a validation request and the API
// operation being validated.
// This type is intended for use with generate validation code and may be enhanced
// in the future to include other information needed to validate requests.
type Operation struct {
// Type is the category of operation being validated. This does not
// differentiate between HTTP verbs like PUT and PATCH, but rather merges
// those into a single "Update" category.
Type Type
// Options declare the options enabled for validation.
//
// Options should be set according to a resource validation strategy before validation
// is performed, and must be treated as read-only during validation.
//
// Options are identified by string names. Option string names may match the name of a feature
// gate, in which case the presence of the name in the set indicates that the feature is
// considered enabled for the resource being validated. Note that a resource may have a
// feature enabled even when the feature gate is disabled. This can happen when feature is
// already in-use by a resource, often because the feature gate was enabled when the
// resource first began using the feature.
//
// Unset options are disabled/false.
Options sets.Set[string]
}
// Code is the request operation to be validated.
type Type uint32
const (
// Create indicates the request being validated is for a resource create operation.
Create Type = iota
// Update indicates the request being validated is for a resource update operation.
Update
)

@ -15,4 +15,4 @@ limitations under the License.
*/
// Package validation contains generic api type validation functions.
package validation // import "k8s.io/apimachinery/pkg/api/validation"
package validation

@ -82,7 +82,7 @@ func maskTrailingDash(name string) string {
func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if value < 0 {
allErrs = append(allErrs, field.Invalid(fldPath, value, IsNegativeErrorMsg))
allErrs = append(allErrs, field.Invalid(fldPath, value, IsNegativeErrorMsg).WithOrigin("minimum"))
}
return allErrs
}

@ -17,4 +17,4 @@ limitations under the License.
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/meta/v1
package internalversion // import "k8s.io/apimachinery/pkg/apis/meta/internalversion"
package internalversion

@ -41,8 +41,6 @@ type ListOptions struct {
// assume bookmarks are returned at any specific interval, nor may they
// assume the server will send any BOOKMARK event during a session.
// If this is not a watch, this field is ignored.
// If the feature gate WatchBookmarks is not enabled in apiserver,
// this field is ignored.
AllowWatchBookmarks bool
// resourceVersion sets a constraint on what resource versions a request may be served from.
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for

@ -21,4 +21,4 @@ limitations under the License.
// +groupName=meta.k8s.io
package v1 // import "k8s.io/apimachinery/pkg/apis/meta/v1"
package v1

@ -20,21 +20,22 @@ limitations under the License.
package v1
import (
"math/rand"
"time"
fuzz "github.com/google/gofuzz"
"sigs.k8s.io/randfill"
)
// Fuzz satisfies fuzz.Interface.
func (t *MicroTime) Fuzz(c fuzz.Continue) {
// Fuzz satisfies randfill.SimpleSelfFiller.
func (t *MicroTime) RandFill(r *rand.Rand) {
if t == nil {
return
}
// Allow for about 1000 years of randomness. Accurate to a tenth of
// micro second. Leave off nanoseconds because JSON doesn't
// represent them so they can't round-trip properly.
t.Time = time.Unix(c.Rand.Int63n(1000*365*24*60*60), 1000*c.Rand.Int63n(1000000))
t.Time = time.Unix(r.Int63n(1000*365*24*60*60), 1000*r.Int63n(1000000))
}
// ensure MicroTime implements fuzz.Interface
var _ fuzz.Interface = &MicroTime{}
// ensure MicroTime implements randfill.Interface
var _ randfill.SimpleSelfFiller = &MicroTime{}

@ -20,21 +20,22 @@ limitations under the License.
package v1
import (
"math/rand"
"time"
fuzz "github.com/google/gofuzz"
"sigs.k8s.io/randfill"
)
// Fuzz satisfies fuzz.Interface.
func (t *Time) Fuzz(c fuzz.Continue) {
// Fuzz satisfies randfill.SimpleSelfFiller.
func (t *Time) RandFill(r *rand.Rand) {
if t == nil {
return
}
// Allow for about 1000 years of randomness. Leave off nanoseconds
// because JSON doesn't represent them so they can't round-trip
// properly.
t.Time = time.Unix(c.Rand.Int63n(1000*365*24*60*60), 0)
t.Time = time.Unix(r.Int63n(1000*365*24*60*60), 0)
}
// ensure Time implements fuzz.Interface
var _ fuzz.Interface = &Time{}
// ensure Time implements randfill.SimpleSelfFiller
var _ randfill.SimpleSelfFiller = &Time{}

@ -188,7 +188,7 @@ func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, b
// NestedStringMap returns a copy of map[string]string value of a nested field.
// Returns false if value is not found and an error if not a map[string]interface{} or contains non-string values in the map.
func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) {
m, found, err := nestedMapNoCopy(obj, fields...)
m, found, err := nestedMapNoCopy(obj, false, fields...)
if !found || err != nil {
return nil, found, err
}
@ -203,10 +203,32 @@ func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]s
return strMap, true, nil
}
// NestedNullCoercingStringMap returns a copy of map[string]string value of a nested field.
// Returns `nil, true, nil` if the value exists and is explicitly null.
// Returns `nil, false, err` if the value is not a map or a null value, or is a map and contains non-string non-null values.
// Null values in the map are coerced to "" to match json decoding behavior.
func NestedNullCoercingStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) {
m, found, err := nestedMapNoCopy(obj, true, fields...)
if !found || err != nil || m == nil {
return nil, found, err
}
strMap := make(map[string]string, len(m))
for k, v := range m {
if str, ok := v.(string); ok {
strMap[k] = str
} else if v == nil {
strMap[k] = ""
} else {
return nil, false, fmt.Errorf("%v accessor error: contains non-string value in the map under key %q: %v is of the type %T, expected string", jsonPath(fields), k, v, v)
}
}
return strMap, true, nil
}
// NestedMap returns a deep copy of map[string]interface{} value of a nested field.
// Returns false if value is not found and an error if not a map[string]interface{}.
func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) {
m, found, err := nestedMapNoCopy(obj, fields...)
m, found, err := nestedMapNoCopy(obj, false, fields...)
if !found || err != nil {
return nil, found, err
}
@ -215,11 +237,14 @@ func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interfa
// nestedMapNoCopy returns a map[string]interface{} value of a nested field.
// Returns false if value is not found and an error if not a map[string]interface{}.
func nestedMapNoCopy(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) {
func nestedMapNoCopy(obj map[string]interface{}, tolerateNil bool, fields ...string) (map[string]interface{}, bool, error) {
val, found, err := NestedFieldNoCopy(obj, fields...)
if !found || err != nil {
return nil, found, err
}
if val == nil && tolerateNil {
return nil, true, nil
}
m, ok := val.(map[string]interface{})
if !ok {
return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]interface{}", jsonPath(fields), val, val)

@ -397,7 +397,7 @@ func (u *Unstructured) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds
}
func (u *Unstructured) GetLabels() map[string]string {
m, _, _ := NestedStringMap(u.Object, "metadata", "labels")
m, _, _ := NestedNullCoercingStringMap(u.Object, "metadata", "labels")
return m
}
@ -410,7 +410,7 @@ func (u *Unstructured) SetLabels(labels map[string]string) {
}
func (u *Unstructured) GetAnnotations() map[string]string {
m, _, _ := NestedStringMap(u.Object, "metadata", "annotations")
m, _, _ := NestedNullCoercingStringMap(u.Object, "metadata", "annotations")
return m
}

@ -104,7 +104,7 @@ func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts L
func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for _, msg := range validation.IsQualifiedName(labelName) {
allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg))
allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg).WithOrigin("labelKey"))
}
return allErrs
}

@ -20,4 +20,4 @@ limitations under the License.
// +groupName=meta.k8s.io
package v1beta1 // import "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
package v1beta1

@ -21,4 +21,4 @@ limitations under the License.
// but for the fields which did not change, copying is automated. This makes it
// easy to modify the structures you use in memory without affecting the format
// you store on disk or respond to in your external API calls.
package conversion // import "k8s.io/apimachinery/pkg/conversion"
package conversion

@ -16,4 +16,4 @@ limitations under the License.
// Package queryparams provides conversion from versioned
// runtime objects to URL query values
package queryparams // import "k8s.io/apimachinery/pkg/conversion/queryparams"
package queryparams

@ -16,4 +16,4 @@ limitations under the License.
// Package fields implements a simple field system, parsing and matching
// selectors with sets of fields.
package fields // import "k8s.io/apimachinery/pkg/fields"
package fields

@ -16,4 +16,4 @@ limitations under the License.
// Package labels implements a simple label system, parsing and matching
// selectors with sets of labels.
package labels // import "k8s.io/apimachinery/pkg/labels"
package labels

@ -48,4 +48,4 @@ limitations under the License.
//
// As a bonus, a few common types useful from all api objects and versions
// are provided in types.go.
package runtime // import "k8s.io/apimachinery/pkg/runtime"
package runtime

@ -259,6 +259,7 @@ type ObjectDefaulter interface {
type ObjectVersioner interface {
ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error)
PrioritizedVersionsForGroup(group string) []schema.GroupVersion
}
// ObjectConvertor converts an object to a different version.

@ -17,15 +17,18 @@ limitations under the License.
package runtime
import (
"context"
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/naming"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// Scheme defines methods for serializing and deserializing API objects, a type
@ -68,6 +71,12 @@ type Scheme struct {
// the provided object must be a pointer.
defaulterFuncs map[reflect.Type]func(interface{})
// validationFuncs is a map to funcs to be called with an object to perform validation.
// The provided object must be a pointer.
// If oldObject is non-nil, update validation is performed and may perform additional
// validation such as transition rules and immutability checks.
validationFuncs map[reflect.Type]func(ctx context.Context, op operation.Operation, object, oldObject interface{}, subresources ...string) field.ErrorList
// converter stores all registered conversion functions. It also has
// default converting behavior.
converter *conversion.Converter
@ -96,6 +105,7 @@ func NewScheme() *Scheme {
unversionedKinds: map[string]reflect.Type{},
fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{},
defaulterFuncs: map[reflect.Type]func(interface{}){},
validationFuncs: map[reflect.Type]func(ctx context.Context, op operation.Operation, object, oldObject interface{}, subresource ...string) field.ErrorList{},
versionPriority: map[string][]string{},
schemeName: naming.GetNameFromCallsite(internalPackages...),
}
@ -347,6 +357,35 @@ func (s *Scheme) Default(src Object) {
}
}
// AddValidationFunc registered a function that can validate the object, and
// oldObject. These functions will be invoked when Validate() or ValidateUpdate()
// is called. The function will never be called unless the validated object
// matches srcType. If this function is invoked twice with the same srcType, the
// fn passed to the later call will be used instead.
func (s *Scheme) AddValidationFunc(srcType Object, fn func(ctx context.Context, op operation.Operation, object, oldObject interface{}, subresources ...string) field.ErrorList) {
s.validationFuncs[reflect.TypeOf(srcType)] = fn
}
// Validate validates the provided Object according to the generated declarative validation code.
// WARNING: This does not validate all objects! The handwritten validation code in validation.go
// is not run when this is called. Only the generated zz_generated.validations.go validation code is run.
func (s *Scheme) Validate(ctx context.Context, options sets.Set[string], object Object, subresources ...string) field.ErrorList {
if fn, ok := s.validationFuncs[reflect.TypeOf(object)]; ok {
return fn(ctx, operation.Operation{Type: operation.Create, Options: options}, object, nil, subresources...)
}
return nil
}
// ValidateUpdate validates the provided object and oldObject according to the generated declarative validation code.
// WARNING: This does not validate all objects! The handwritten validation code in validation.go
// is not run when this is called. Only the generated zz_generated.validations.go validation code is run.
func (s *Scheme) ValidateUpdate(ctx context.Context, options sets.Set[string], object, oldObject Object, subresources ...string) field.ErrorList {
if fn, ok := s.validationFuncs[reflect.TypeOf(object)]; ok {
return fn(ctx, operation.Operation{Type: operation.Update, Options: options}, object, oldObject, subresources...)
}
return nil
}
// Convert will attempt to convert in into out. Both must be pointers. For easy
// testing of conversion functions. Returns an error if the conversion isn't
// possible. You can call this with types that haven't been registered (for example,

@ -140,7 +140,7 @@ func (cache *checkers) getCheckerInternal(rt reflect.Type, parent *path) (c chec
var wg sync.WaitGroup
wg.Add(1)
defer wg.Done()
c = checker{
placeholder := checker{
safe: func() bool {
wg.Wait()
return c.safe()
@ -150,7 +150,7 @@ func (cache *checkers) getCheckerInternal(rt reflect.Type, parent *path) (c chec
return c.check(rv, depth)
},
}
if actual, loaded := cache.m.LoadOrStore(rt, &c); loaded {
if actual, loaded := cache.m.LoadOrStore(rt, &placeholder); loaded {
// Someone else stored an entry for this type, use it.
return *actual.(*checker)
}

@ -28,7 +28,7 @@ import (
func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory, options CodecFactoryOptions) []runtime.SerializerInfo {
jsonSerializer := json.NewSerializerWithOptions(
mf, scheme, scheme,
json.SerializerOptions{Yaml: false, Pretty: false, Strict: options.Strict},
json.SerializerOptions{Yaml: false, Pretty: false, Strict: options.Strict, StreamingCollectionsEncoding: options.StreamingCollectionsEncodingToJSON},
)
jsonSerializerType := runtime.SerializerInfo{
MediaType: runtime.ContentTypeJSON,
@ -38,7 +38,7 @@ func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory, option
Serializer: jsonSerializer,
StrictSerializer: json.NewSerializerWithOptions(
mf, scheme, scheme,
json.SerializerOptions{Yaml: false, Pretty: false, Strict: true},
json.SerializerOptions{Yaml: false, Pretty: false, Strict: true, StreamingCollectionsEncoding: options.StreamingCollectionsEncodingToJSON},
),
StreamSerializer: &runtime.StreamSerializerInfo{
EncodesAsText: true,
@ -61,7 +61,9 @@ func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory, option
mf, scheme, scheme,
json.SerializerOptions{Yaml: true, Pretty: false, Strict: true},
)
protoSerializer := protobuf.NewSerializer(scheme, scheme)
protoSerializer := protobuf.NewSerializerWithOptions(scheme, scheme, protobuf.SerializerOptions{
StreamingCollectionsEncoding: options.StreamingCollectionsEncodingToProtobuf,
})
protoRawSerializer := protobuf.NewRawSerializer(scheme, scheme)
serializers := []runtime.SerializerInfo{
@ -113,6 +115,9 @@ type CodecFactoryOptions struct {
// Pretty includes a pretty serializer along with the non-pretty one
Pretty bool
StreamingCollectionsEncodingToJSON bool
StreamingCollectionsEncodingToProtobuf bool
serializers []func(runtime.ObjectCreater, runtime.ObjectTyper) runtime.SerializerInfo
}
@ -147,6 +152,18 @@ func WithSerializer(f func(runtime.ObjectCreater, runtime.ObjectTyper) runtime.S
}
}
func WithStreamingCollectionEncodingToJSON() CodecFactoryOptionsMutator {
return func(options *CodecFactoryOptions) {
options.StreamingCollectionsEncodingToJSON = true
}
}
func WithStreamingCollectionEncodingToProtobuf() CodecFactoryOptionsMutator {
return func(options *CodecFactoryOptions) {
options.StreamingCollectionsEncodingToProtobuf = true
}
}
// NewCodecFactory provides methods for retrieving serializers for the supported wire formats
// and conversion wrappers to define preferred internal and external versions. In the future,
// as the internal version is used less, callers may instead use a defaulting serializer and

@ -0,0 +1,230 @@
/*
Copyright 2025 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 json
import (
"encoding/json"
"fmt"
"io"
"maps"
"slices"
"sort"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/conversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
func streamEncodeCollections(obj runtime.Object, w io.Writer) (bool, error) {
list, ok := obj.(*unstructured.UnstructuredList)
if ok {
return true, streamingEncodeUnstructuredList(w, list)
}
if _, ok := obj.(json.Marshaler); ok {
return false, nil
}
typeMeta, listMeta, items, err := getListMeta(obj)
if err == nil {
return true, streamingEncodeList(w, typeMeta, listMeta, items)
}
return false, nil
}
// getListMeta implements list extraction logic for json stream serialization.
//
// Reason for a custom logic instead of reusing accessors from meta package:
// * Validate json tags to prevent incompatibility with json standard package.
// * ListMetaAccessor doesn't distinguish empty from nil value.
// * TypeAccessort reparsing "apiVersion" and serializing it with "{group}/{version}"
func getListMeta(list runtime.Object) (metav1.TypeMeta, metav1.ListMeta, []runtime.Object, error) {
listValue, err := conversion.EnforcePtr(list)
if err != nil {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err
}
listType := listValue.Type()
if listType.NumField() != 3 {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListType to have 3 fields")
}
// TypeMeta
typeMeta, ok := listValue.Field(0).Interface().(metav1.TypeMeta)
if !ok {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected TypeMeta field to have TypeMeta type")
}
if listType.Field(0).Tag.Get("json") != ",inline" {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag to be ",inline"`)
}
// ListMeta
listMeta, ok := listValue.Field(1).Interface().(metav1.ListMeta)
if !ok {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListMeta field to have ListMeta type")
}
if listType.Field(1).Tag.Get("json") != "metadata,omitempty" {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected ListMeta json field tag to be "metadata,omitempty"`)
}
// Items
items, err := meta.ExtractList(list)
if err != nil {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err
}
if listType.Field(2).Tag.Get("json") != "items" {
return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected Items json field tag to be "items"`)
}
return typeMeta, listMeta, items, nil
}
func streamingEncodeList(w io.Writer, typeMeta metav1.TypeMeta, listMeta metav1.ListMeta, items []runtime.Object) error {
// Start
if _, err := w.Write([]byte(`{`)); err != nil {
return err
}
// TypeMeta
if typeMeta.Kind != "" {
if err := encodeKeyValuePair(w, "kind", typeMeta.Kind, []byte(",")); err != nil {
return err
}
}
if typeMeta.APIVersion != "" {
if err := encodeKeyValuePair(w, "apiVersion", typeMeta.APIVersion, []byte(",")); err != nil {
return err
}
}
// ListMeta
if err := encodeKeyValuePair(w, "metadata", listMeta, []byte(",")); err != nil {
return err
}
// Items
if err := encodeItemsObjectSlice(w, items); err != nil {
return err
}
// End
_, err := w.Write([]byte("}\n"))
return err
}
func encodeItemsObjectSlice(w io.Writer, items []runtime.Object) (err error) {
if items == nil {
err := encodeKeyValuePair(w, "items", nil, nil)
return err
}
_, err = w.Write([]byte(`"items":[`))
if err != nil {
return err
}
suffix := []byte(",")
for i, item := range items {
if i == len(items)-1 {
suffix = nil
}
err := encodeValue(w, item, suffix)
if err != nil {
return err
}
}
_, err = w.Write([]byte("]"))
if err != nil {
return err
}
return err
}
func streamingEncodeUnstructuredList(w io.Writer, list *unstructured.UnstructuredList) error {
_, err := w.Write([]byte(`{`))
if err != nil {
return err
}
keys := slices.Collect(maps.Keys(list.Object))
if _, exists := list.Object["items"]; !exists {
keys = append(keys, "items")
}
sort.Strings(keys)
suffix := []byte(",")
for i, key := range keys {
if i == len(keys)-1 {
suffix = nil
}
if key == "items" {
err = encodeItemsUnstructuredSlice(w, list.Items, suffix)
} else {
err = encodeKeyValuePair(w, key, list.Object[key], suffix)
}
if err != nil {
return err
}
}
_, err = w.Write([]byte("}\n"))
return err
}
func encodeItemsUnstructuredSlice(w io.Writer, items []unstructured.Unstructured, suffix []byte) (err error) {
_, err = w.Write([]byte(`"items":[`))
if err != nil {
return err
}
comma := []byte(",")
for i, item := range items {
if i == len(items)-1 {
comma = nil
}
err := encodeValue(w, item.Object, comma)
if err != nil {
return err
}
}
_, err = w.Write([]byte("]"))
if err != nil {
return err
}
if len(suffix) > 0 {
_, err = w.Write(suffix)
}
return err
}
func encodeKeyValuePair(w io.Writer, key string, value any, suffix []byte) (err error) {
err = encodeValue(w, key, []byte(":"))
if err != nil {
return err
}
err = encodeValue(w, value, suffix)
if err != nil {
return err
}
return err
}
func encodeValue(w io.Writer, value any, suffix []byte) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
_, err = w.Write(data)
if err != nil {
return err
}
if len(suffix) > 0 {
_, err = w.Write(suffix)
}
return err
}

@ -36,7 +36,7 @@ import (
// is not nil, the object has the group, version, and kind fields set.
// Deprecated: use NewSerializerWithOptions instead.
func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, pretty bool) *Serializer {
return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{false, pretty, false})
return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{false, pretty, false, false})
}
// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer
@ -44,7 +44,7 @@ func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtim
// matches JSON, and will error if constructs are used that do not serialize to JSON.
// Deprecated: use NewSerializerWithOptions instead.
func NewYAMLSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer {
return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{true, false, false})
return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{true, false, false, false})
}
// NewSerializerWithOptions creates a JSON/YAML serializer that handles encoding versioned objects into the proper JSON/YAML
@ -93,6 +93,9 @@ type SerializerOptions struct {
// Strict: configures the Serializer to return strictDecodingError's when duplicate fields are present decoding JSON or YAML.
// Note that enabling this option is not as performant as the non-strict variant, and should not be used in fast paths.
Strict bool
// StreamingCollectionsEncoding enables encoding collection, one item at the time, drastically reducing memory needed.
StreamingCollectionsEncoding bool
}
// Serializer handles encoding versioned objects into the proper JSON form
@ -242,6 +245,15 @@ func (s *Serializer) doEncode(obj runtime.Object, w io.Writer) error {
_, err = w.Write(data)
return err
}
if s.options.StreamingCollectionsEncoding {
ok, err := streamEncodeCollections(obj, w)
if err != nil {
return err
}
if ok {
return nil
}
}
encoder := json.NewEncoder(w)
return encoder.Encode(obj)
}

@ -0,0 +1,174 @@
/*
Copyright 2025 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 protobuf
import (
"errors"
"io"
"math/bits"
"github.com/gogo/protobuf/proto"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
)
var (
errFieldCount = errors.New("expected ListType to have 3 fields")
errTypeMetaField = errors.New("expected TypeMeta field to have TypeMeta type")
errTypeMetaProtobufTag = errors.New(`expected TypeMeta protobuf field tag to be ""`)
errListMetaField = errors.New("expected ListMeta field to have ListMeta type")
errListMetaProtobufTag = errors.New(`expected ListMeta protobuf field tag to be "bytes,1,opt,name=metadata"`)
errItemsProtobufTag = errors.New(`expected Items protobuf field tag to be "bytes,2,rep,name=items"`)
errItemsSizer = errors.New(`expected Items elements to implement proto.Sizer`)
)
// getStreamingListData implements list extraction logic for protobuf stream serialization.
//
// Reason for a custom logic instead of reusing accessors from meta package:
// * Validate proto tags to prevent incompatibility with proto standard package.
// * ListMetaAccessor doesn't distinguish empty from nil value.
// * TypeAccessor reparsing "apiVersion" and serializing it with "{group}/{version}"
func getStreamingListData(list runtime.Object) (data streamingListData, err error) {
listValue, err := conversion.EnforcePtr(list)
if err != nil {
return data, err
}
listType := listValue.Type()
if listType.NumField() != 3 {
return data, errFieldCount
}
// TypeMeta: validated, but not returned as is not serialized.
_, ok := listValue.Field(0).Interface().(metav1.TypeMeta)
if !ok {
return data, errTypeMetaField
}
if listType.Field(0).Tag.Get("protobuf") != "" {
return data, errTypeMetaProtobufTag
}
// ListMeta
listMeta, ok := listValue.Field(1).Interface().(metav1.ListMeta)
if !ok {
return data, errListMetaField
}
// if we were ever to relax the protobuf tag check we should update the hardcoded `0xa` below when writing ListMeta.
if listType.Field(1).Tag.Get("protobuf") != "bytes,1,opt,name=metadata" {
return data, errListMetaProtobufTag
}
data.listMeta = listMeta
// Items; if we were ever to relax the protobuf tag check we should update the hardcoded `0x12` below when writing Items.
if listType.Field(2).Tag.Get("protobuf") != "bytes,2,rep,name=items" {
return data, errItemsProtobufTag
}
items, err := meta.ExtractList(list)
if err != nil {
return data, err
}
data.items = items
data.totalSize, data.listMetaSize, data.itemsSizes, err = listSize(listMeta, items)
return data, err
}
type streamingListData struct {
// totalSize is the total size of the serialized List object, including their proto headers/size bytes
totalSize int
// listMetaSize caches results from .Size() call to listMeta, doesn't include header bytes (field identifier, size)
listMetaSize int
listMeta metav1.ListMeta
// itemsSizes caches results from .Size() call to items, doesn't include header bytes (field identifier, size)
itemsSizes []int
items []runtime.Object
}
// listSize return size of ListMeta and items to be later used for preallocations.
// listMetaSize and itemSizes do not include header bytes (field identifier, size).
func listSize(listMeta metav1.ListMeta, items []runtime.Object) (totalSize, listMetaSize int, itemSizes []int, err error) {
// ListMeta
listMetaSize = listMeta.Size()
totalSize += 1 + sovGenerated(uint64(listMetaSize)) + listMetaSize
// Items
itemSizes = make([]int, len(items))
for i, item := range items {
sizer, ok := item.(proto.Sizer)
if !ok {
return totalSize, listMetaSize, nil, errItemsSizer
}
n := sizer.Size()
itemSizes[i] = n
totalSize += 1 + sovGenerated(uint64(n)) + n
}
return totalSize, listMetaSize, itemSizes, nil
}
func streamingEncodeUnknownList(w io.Writer, unk runtime.Unknown, listData streamingListData, memAlloc runtime.MemoryAllocator) error {
_, err := w.Write(protoEncodingPrefix)
if err != nil {
return err
}
// encodeList is responsible for encoding the List into the unknown Raw.
encodeList := func(writer io.Writer) (int, error) {
return streamingEncodeList(writer, listData, memAlloc)
}
_, err = unk.MarshalToWriter(w, listData.totalSize, encodeList)
return err
}
func streamingEncodeList(w io.Writer, listData streamingListData, memAlloc runtime.MemoryAllocator) (size int, err error) {
// ListMeta; 0xa = (1 << 3) | 2; field number: 1, type: 2 (LEN). https://protobuf.dev/programming-guides/encoding/#structure
n, err := doEncodeWithHeader(&listData.listMeta, w, 0xa, listData.listMetaSize, memAlloc)
size += n
if err != nil {
return size, err
}
// Items; 0x12 = (2 << 3) | 2; field number: 2, type: 2 (LEN). https://protobuf.dev/programming-guides/encoding/#structure
for i, item := range listData.items {
n, err := doEncodeWithHeader(item, w, 0x12, listData.itemsSizes[i], memAlloc)
size += n
if err != nil {
return size, err
}
}
return size, nil
}
func writeVarintGenerated(w io.Writer, v int) (int, error) {
buf := make([]byte, sovGenerated(uint64(v)))
encodeVarintGenerated(buf, len(buf), uint64(v))
return w.Write(buf)
}
// sovGenerated is copied from `generated.pb.go` returns size of varint.
func sovGenerated(v uint64) int {
return (bits.Len64(v|1) + 6) / 7
}
// encodeVarintGenerated is copied from `generated.pb.go` encodes varint.
func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
offset -= sovGenerated(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}

@ -15,4 +15,4 @@ limitations under the License.
*/
// Package protobuf provides a Kubernetes serializer for the protobuf format.
package protobuf // import "k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
package protobuf

@ -72,10 +72,18 @@ func IsNotMarshalable(err error) bool {
// is passed, the encoded object will have group, version, and kind fields set. If typer is nil, the objects will be written
// as-is (any type info passed with the object will be used).
func NewSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer {
return NewSerializerWithOptions(creater, typer, SerializerOptions{})
}
// NewSerializerWithOptions creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If a typer
// is passed, the encoded object will have group, version, and kind fields set. If typer is nil, the objects will be written
// as-is (any type info passed with the object will be used).
func NewSerializerWithOptions(creater runtime.ObjectCreater, typer runtime.ObjectTyper, opts SerializerOptions) *Serializer {
return &Serializer{
prefix: protoEncodingPrefix,
creater: creater,
typer: typer,
options: opts,
}
}
@ -84,6 +92,14 @@ type Serializer struct {
prefix []byte
creater runtime.ObjectCreater
typer runtime.ObjectTyper
options SerializerOptions
}
// SerializerOptions holds the options which are used to configure a Proto serializer.
type SerializerOptions struct {
// StreamingCollectionsEncoding enables encoding collection, one item at the time, drastically reducing memory needed.
StreamingCollectionsEncoding bool
}
var _ runtime.Serializer = &Serializer{}
@ -209,6 +225,13 @@ func (s *Serializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.
},
}
}
if s.options.StreamingCollectionsEncoding {
listData, err := getStreamingListData(obj)
if err == nil {
// Doesn't honor custom proto marshaling methods (like json streaming), because all proto objects implement proto methods.
return streamingEncodeUnknownList(w, unk, listData, memAlloc)
}
}
switch t := obj.(type) {
case bufferedMarshaller:
@ -428,6 +451,39 @@ func (s *RawSerializer) encode(obj runtime.Object, w io.Writer, memAlloc runtime
}
func (s *RawSerializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error {
_, err := doEncode(obj, w, nil, memAlloc)
return err
}
func doEncodeWithHeader(obj any, w io.Writer, field byte, precomputedSize int, memAlloc runtime.MemoryAllocator) (size int, err error) {
// Field identifier
n, err := w.Write([]byte{field})
size += n
if err != nil {
return size, err
}
// Size
n, err = writeVarintGenerated(w, precomputedSize)
size += n
if err != nil {
return size, err
}
// Obj
n, err = doEncode(obj, w, &precomputedSize, memAlloc)
size += n
if err != nil {
return size, err
}
if n != precomputedSize {
return size, fmt.Errorf("the size value was %d, but doEncode wrote %d bytes to data", precomputedSize, n)
}
return size, nil
}
// doEncode encodes provided object into writer using a allocator if possible.
// Avoids call by object Size if precomputedObjSize is provided.
// precomputedObjSize should not include header bytes (field identifier, size).
func doEncode(obj any, w io.Writer, precomputedObjSize *int, memAlloc runtime.MemoryAllocator) (int, error) {
if memAlloc == nil {
klog.Error("a mandatory memory allocator wasn't provided, this might have a negative impact on performance, check invocations of EncodeWithAllocator method, falling back on runtime.SimpleAllocator")
memAlloc = &runtime.SimpleAllocator{}
@ -436,40 +492,43 @@ func (s *RawSerializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runti
case bufferedReverseMarshaller:
// this path performs a single allocation during write only when the Allocator wasn't provided
// it also requires the caller to implement the more efficient Size and MarshalToSizedBuffer methods
encodedSize := uint64(t.Size())
data := memAlloc.Allocate(encodedSize)
if precomputedObjSize == nil {
s := t.Size()
precomputedObjSize = &s
}
data := memAlloc.Allocate(uint64(*precomputedObjSize))
n, err := t.MarshalToSizedBuffer(data)
if err != nil {
return err
return 0, err
}
_, err = w.Write(data[:n])
return err
return w.Write(data[:n])
case bufferedMarshaller:
// this path performs a single allocation during write only when the Allocator wasn't provided
// it also requires the caller to implement the more efficient Size and MarshalTo methods
encodedSize := uint64(t.Size())
data := memAlloc.Allocate(encodedSize)
if precomputedObjSize == nil {
s := t.Size()
precomputedObjSize = &s
}
data := memAlloc.Allocate(uint64(*precomputedObjSize))
n, err := t.MarshalTo(data)
if err != nil {
return err
return 0, err
}
_, err = w.Write(data[:n])
return err
return w.Write(data[:n])
case proto.Marshaler:
// this path performs extra allocations
data, err := t.Marshal()
if err != nil {
return err
return 0, err
}
_, err = w.Write(data)
return err
return w.Write(data)
default:
return errNotMarshalable{reflect.TypeOf(obj)}
return 0, errNotMarshalable{reflect.TypeOf(obj)}
}
}

@ -18,6 +18,7 @@ package runtime
import (
"fmt"
"io"
)
type ProtobufMarshaller interface {
@ -28,6 +29,124 @@ type ProtobufReverseMarshaller interface {
MarshalToSizedBuffer(data []byte) (int, error)
}
const (
typeMetaTag = 0xa
rawTag = 0x12
contentEncodingTag = 0x1a
contentTypeTag = 0x22
// max length of a varint for a uint64
maxUint64VarIntLength = 10
)
// MarshalToWriter allows a caller to provide a streaming writer for raw bytes,
// instead of populating them inside the Unknown struct.
// rawSize is the number of bytes rawWriter will write in a success case.
// writeRaw is called when it is time to write the raw bytes. It must return `rawSize, nil` or an error.
func (m *Unknown) MarshalToWriter(w io.Writer, rawSize int, writeRaw func(io.Writer) (int, error)) (int, error) {
size := 0
// reuse the buffer for varint marshaling
varintBuffer := make([]byte, maxUint64VarIntLength)
writeVarint := func(i int) (int, error) {
offset := encodeVarintGenerated(varintBuffer, len(varintBuffer), uint64(i))
return w.Write(varintBuffer[offset:])
}
// TypeMeta
{
n, err := w.Write([]byte{typeMetaTag})
size += n
if err != nil {
return size, err
}
typeMetaBytes, err := m.TypeMeta.Marshal()
if err != nil {
return size, err
}
n, err = writeVarint(len(typeMetaBytes))
size += n
if err != nil {
return size, err
}
n, err = w.Write(typeMetaBytes)
size += n
if err != nil {
return size, err
}
}
// Raw, delegating write to writeRaw()
{
n, err := w.Write([]byte{rawTag})
size += n
if err != nil {
return size, err
}
n, err = writeVarint(rawSize)
size += n
if err != nil {
return size, err
}
n, err = writeRaw(w)
size += n
if err != nil {
return size, err
}
if n != int(rawSize) {
return size, fmt.Errorf("the size value was %d, but encoding wrote %d bytes to data", rawSize, n)
}
}
// ContentEncoding
{
n, err := w.Write([]byte{contentEncodingTag})
size += n
if err != nil {
return size, err
}
n, err = writeVarint(len(m.ContentEncoding))
size += n
if err != nil {
return size, err
}
n, err = w.Write([]byte(m.ContentEncoding))
size += n
if err != nil {
return size, err
}
}
// ContentEncoding
{
n, err := w.Write([]byte{contentTypeTag})
size += n
if err != nil {
return size, err
}
n, err = writeVarint(len(m.ContentType))
size += n
if err != nil {
return size, err
}
n, err = w.Write([]byte(m.ContentType))
size += n
if err != nil {
return size, err
}
}
return size, nil
}
// NestedMarshalTo allows a caller to avoid extra allocations during serialization of an Unknown
// that will contain an object that implements ProtobufMarshaller or ProtobufReverseMarshaller.
func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64) (int, error) {
@ -43,12 +162,12 @@ func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64
copy(data[i:], m.ContentType)
i = encodeVarintGenerated(data, i, uint64(len(m.ContentType)))
i--
data[i] = 0x22
data[i] = contentTypeTag
i -= len(m.ContentEncoding)
copy(data[i:], m.ContentEncoding)
i = encodeVarintGenerated(data, i, uint64(len(m.ContentEncoding)))
i--
data[i] = 0x1a
data[i] = contentEncodingTag
if b != nil {
if r, ok := b.(ProtobufReverseMarshaller); ok {
n1, err := r.MarshalToSizedBuffer(data[:i])
@ -75,7 +194,7 @@ func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64
}
i = encodeVarintGenerated(data, i, size)
i--
data[i] = 0x12
data[i] = rawTag
}
n2, err := m.TypeMeta.MarshalToSizedBuffer(data[:i])
if err != nil {
@ -84,6 +203,6 @@ func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64
i -= n2
i = encodeVarintGenerated(data, i, uint64(n2))
i--
data[i] = 0xa
data[i] = typeMetaTag
return msgSize - i, nil
}

@ -15,4 +15,4 @@ limitations under the License.
*/
// Package types implements various generic types used throughout kubernetes.
package types // import "k8s.io/apimachinery/pkg/types"
package types

@ -23,7 +23,7 @@ import (
"strings"
"text/tabwriter"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp" //nolint:depguard
"k8s.io/apimachinery/pkg/util/dump"
)

@ -15,4 +15,4 @@ limitations under the License.
*/
// Package errors implements various utility functions and types around errors.
package errors // import "k8s.io/apimachinery/pkg/util/errors"
package errors

@ -91,12 +91,12 @@ func (r *lengthDelimitedFrameReader) Read(data []byte) (int, error) {
}
n, err := io.ReadAtLeast(r.r, data[:max], int(max))
r.remaining -= n
if err == io.ErrShortBuffer || r.remaining > 0 {
return n, io.ErrShortBuffer
}
if err != nil {
return n, err
}
if r.remaining > 0 {
return n, io.ErrShortBuffer
}
if n != expect {
return n, io.ErrUnexpectedEOF
}

@ -20,24 +20,24 @@ limitations under the License.
package intstr
import (
fuzz "github.com/google/gofuzz"
"sigs.k8s.io/randfill"
)
// Fuzz satisfies fuzz.Interface
func (intstr *IntOrString) Fuzz(c fuzz.Continue) {
// RandFill satisfies randfill.NativeSelfFiller
func (intstr *IntOrString) RandFill(c randfill.Continue) {
if intstr == nil {
return
}
if c.RandBool() {
if c.Bool() {
intstr.Type = Int
c.Fuzz(&intstr.IntVal)
c.Fill(&intstr.IntVal)
intstr.StrVal = ""
} else {
intstr.Type = String
intstr.IntVal = 0
c.Fuzz(&intstr.StrVal)
c.Fill(&intstr.StrVal)
}
}
// ensure IntOrString implements fuzz.Interface
var _ fuzz.Interface = &IntOrString{}
var _ randfill.NativeSelfFiller = &IntOrString{}

@ -36,6 +36,11 @@ var (
)
// PanicHandlers is a list of functions which will be invoked when a panic happens.
//
// The code invoking these handlers prepares a contextual logger so that
// klog.FromContext(ctx) already skips over the panic handler itself and
// several other intermediate functions, ideally such that the log output
// is attributed to the code which triggered the panic.
var PanicHandlers = []func(context.Context, interface{}){logPanic}
// HandleCrash simply catches a crash and logs an error. Meant to be called via
@ -45,7 +50,7 @@ var PanicHandlers = []func(context.Context, interface{}){logPanic}
//
// E.g., you can provide one or more additional handlers for something like shutting down go routines gracefully.
//
// Contextual logging: HandleCrashWithContext should be used instead of HandleCrash in code which supports contextual logging.
// Contextual logging: HandleCrashWithContext or HandleCrashWithLogger should be used instead of HandleCrash in code which supports contextual logging.
func HandleCrash(additionalHandlers ...func(interface{})) {
if r := recover(); r != nil {
additionalHandlersWithContext := make([]func(context.Context, interface{}), len(additionalHandlers))
@ -74,10 +79,30 @@ func HandleCrashWithContext(ctx context.Context, additionalHandlers ...func(cont
}
}
// handleCrash is the common implementation of HandleCrash and HandleCrash.
// HandleCrashWithLogger simply catches a crash and logs an error. Meant to be called via
// defer. Additional context-specific handlers can be provided, and will be
// called in case of panic. HandleCrash actually crashes, after calling the
// handlers and logging the panic message.
//
// E.g., you can provide one or more additional handlers for something like shutting down go routines gracefully.
func HandleCrashWithLogger(logger klog.Logger, additionalHandlers ...func(context.Context, interface{})) {
if r := recover(); r != nil {
ctx := klog.NewContext(context.Background(), logger)
handleCrash(ctx, r, additionalHandlers...)
}
}
// handleCrash is the common implementation of the HandleCrash* variants.
// Having those call a common implementation ensures that the stack depth
// is the same regardless through which path the handlers get invoked.
func handleCrash(ctx context.Context, r any, additionalHandlers ...func(context.Context, interface{})) {
// We don't really know how many call frames to skip because the Go
// panic handler is between us and the code where the panic occurred.
// If it's one function (as in Go 1.21), then skipping four levels
// gets us to the function which called the `defer HandleCrashWithontext(...)`.
logger := klog.FromContext(ctx).WithCallDepth(4)
ctx = klog.NewContext(ctx, logger)
for _, fn := range PanicHandlers {
fn(ctx, r)
}
@ -106,11 +131,7 @@ func logPanic(ctx context.Context, r interface{}) {
stacktrace := make([]byte, size)
stacktrace = stacktrace[:runtime.Stack(stacktrace, false)]
// We don't really know how many call frames to skip because the Go
// panic handler is between us and the code where the panic occurred.
// If it's one function (as in Go 1.21), then skipping four levels
// gets us to the function which called the `defer HandleCrashWithontext(...)`.
logger := klog.FromContext(ctx).WithCallDepth(4)
logger := klog.FromContext(ctx)
// For backwards compatibility, conversion to string
// is handled here instead of defering to the logging
@ -176,12 +197,19 @@ func HandleError(err error) {
// and key/value pairs.
//
// This variant should be used instead of HandleError because it supports
// structured, contextual logging.
// structured, contextual logging. Alternatively, [HandleErrorWithLogger] can
// be used if a logger is available instead of a context.
func HandleErrorWithContext(ctx context.Context, err error, msg string, keysAndValues ...interface{}) {
handleError(ctx, err, msg, keysAndValues...)
}
// handleError is the common implementation of HandleError and HandleErrorWithContext.
// HandleErrorWithLogger is an alternative to [HandlerErrorWithContext] which accepts
// a logger for contextual logging.
func HandleErrorWithLogger(logger klog.Logger, err error, msg string, keysAndValues ...interface{}) {
handleError(klog.NewContext(context.Background(), logger), err, msg, keysAndValues...)
}
// handleError is the common implementation of the HandleError* variants.
// Using this common implementation ensures that the stack depth
// is the same regardless through which path the handlers get invoked.
func handleError(ctx context.Context, err error, msg string, keysAndValues ...interface{}) {

@ -16,4 +16,4 @@ limitations under the License.
// Package sets has generic set and specified sets. Generic set will
// replace specified ones over time. And specific ones are deprecated.
package sets // import "k8s.io/apimachinery/pkg/util/sets"
package sets

@ -0,0 +1,212 @@
/*
Copyright 2025 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 field
import (
"fmt"
"reflect"
"regexp"
"strings"
)
// ErrorMatcher is a helper for comparing Error objects.
type ErrorMatcher struct {
// TODO(thockin): consider whether type is ever NOT required, maybe just
// assume it.
matchType bool
// TODO(thockin): consider whether field could be assumed - if the
// "want" error has a nil field, don't match on field.
matchField bool
// TODO(thockin): consider whether value could be assumed - if the
// "want" error has a nil value, don't match on field.
matchValue bool
matchOrigin bool
matchDetail func(want, got string) bool
requireOriginWhenInvalid bool
}
// Matches returns true if the two Error objects match according to the
// configured criteria.
func (m ErrorMatcher) Matches(want, got *Error) bool {
if m.matchType && want.Type != got.Type {
return false
}
if m.matchField && want.Field != got.Field {
return false
}
if m.matchValue && !reflect.DeepEqual(want.BadValue, got.BadValue) {
return false
}
if m.matchOrigin {
if want.Origin != got.Origin {
return false
}
if m.requireOriginWhenInvalid && want.Type == ErrorTypeInvalid {
if want.Origin == "" || got.Origin == "" {
return false
}
}
}
if m.matchDetail != nil && !m.matchDetail(want.Detail, got.Detail) {
return false
}
return true
}
// Render returns a string representation of the specified Error object,
// according to the criteria configured in the ErrorMatcher.
func (m ErrorMatcher) Render(e *Error) string {
buf := strings.Builder{}
comma := func() {
if buf.Len() > 0 {
buf.WriteString(", ")
}
}
if m.matchType {
comma()
buf.WriteString(fmt.Sprintf("Type=%q", e.Type))
}
if m.matchField {
comma()
buf.WriteString(fmt.Sprintf("Field=%q", e.Field))
}
if m.matchValue {
comma()
buf.WriteString(fmt.Sprintf("Value=%v", e.BadValue))
}
if m.matchOrigin || m.requireOriginWhenInvalid && e.Type == ErrorTypeInvalid {
comma()
buf.WriteString(fmt.Sprintf("Origin=%q", e.Origin))
}
if m.matchDetail != nil {
comma()
buf.WriteString(fmt.Sprintf("Detail=%q", e.Detail))
}
return "{" + buf.String() + "}"
}
// Exactly returns a derived ErrorMatcher which matches all fields exactly.
func (m ErrorMatcher) Exactly() ErrorMatcher {
return m.ByType().ByField().ByValue().ByOrigin().ByDetailExact()
}
// ByType returns a derived ErrorMatcher which also matches by type.
func (m ErrorMatcher) ByType() ErrorMatcher {
m.matchType = true
return m
}
// ByField returns a derived ErrorMatcher which also matches by field path.
func (m ErrorMatcher) ByField() ErrorMatcher {
m.matchField = true
return m
}
// ByValue returns a derived ErrorMatcher which also matches by the errant
// value.
func (m ErrorMatcher) ByValue() ErrorMatcher {
m.matchValue = true
return m
}
// ByOrigin returns a derived ErrorMatcher which also matches by the origin.
func (m ErrorMatcher) ByOrigin() ErrorMatcher {
m.matchOrigin = true
return m
}
// RequireOriginWhenInvalid returns a derived ErrorMatcher which also requires
// the Origin field to be set when the Type is Invalid and the matcher is
// matching by Origin.
func (m ErrorMatcher) RequireOriginWhenInvalid() ErrorMatcher {
m.requireOriginWhenInvalid = true
return m
}
// ByDetailExact returns a derived ErrorMatcher which also matches errors by
// the exact detail string.
func (m ErrorMatcher) ByDetailExact() ErrorMatcher {
m.matchDetail = func(want, got string) bool {
return got == want
}
return m
}
// ByDetailSubstring returns a derived ErrorMatcher which also matches errors
// by a substring of the detail string.
func (m ErrorMatcher) ByDetailSubstring() ErrorMatcher {
m.matchDetail = func(want, got string) bool {
return strings.Contains(got, want)
}
return m
}
// ByDetailRegexp returns a derived ErrorMatcher which also matches errors by a
// regular expression of the detail string, where the "want" string is assumed
// to be a valid regular expression.
func (m ErrorMatcher) ByDetailRegexp() ErrorMatcher {
m.matchDetail = func(want, got string) bool {
return regexp.MustCompile(want).MatchString(got)
}
return m
}
// TestIntf lets users pass a testing.T while not coupling this package to Go's
// testing package.
type TestIntf interface {
Helper()
Errorf(format string, args ...any)
Logf(format string, args ...any)
}
// Test compares two ErrorLists by the criteria configured in this matcher, and
// fails the test if they don't match. If a given "want" error matches multiple
// "got" errors, they will all be consumed. This might be OK (e.g. if there are
// multiple errors on the same field from the same origin) or it might be an
// insufficiently specific matcher, so these will be logged.
func (m ErrorMatcher) Test(tb TestIntf, want, got ErrorList) {
tb.Helper()
remaining := got
for _, w := range want {
tmp := make(ErrorList, 0, len(remaining))
n := 0
for _, g := range remaining {
if m.Matches(w, g) {
n++
} else {
tmp = append(tmp, g)
}
}
if n == 0 {
tb.Errorf("expected an error matching:\n%s", m.Render(w))
} else if n > 1 {
// This is not necessarily and error, but it's worth logging in
// case it's not what the test author intended.
tb.Logf("multiple errors matched:\n%s", m.Render(w))
}
remaining = tmp
}
if len(remaining) > 0 {
for _, e := range remaining {
exactly := m.Exactly() // makes a copy
tb.Errorf("unmatched error:\n%s", exactly.Render(e))
}
}
}

@ -33,13 +33,35 @@ type Error struct {
Field string
BadValue interface{}
Detail string
// Origin uniquely identifies where this error was generated from. It is used in testing to
// compare expected errors against actual errors without relying on exact detail string matching.
// This allows tests to verify the correct validation logic triggered the error
// regardless of how the error message might be formatted or localized.
//
// The value should be either:
// - A simple camelCase identifier (e.g., "maximum", "maxItems")
// - A structured format using "format=<dash-style-identifier>" for validation errors related to specific formats
// (e.g., "format=dns-label", "format=qualified-name")
//
// If the Origin corresponds to an existing declarative validation tag or JSON Schema keyword,
// use that same name for consistency.
//
// Origin should be set in the most deeply nested validation function that
// can still identify the unique source of the error.
Origin string
// CoveredByDeclarative is true when this error is covered by declarative
// validation. This field is to identify errors from imperative validation
// that should also be caught by declarative validation.
CoveredByDeclarative bool
}
var _ error = &Error{}
// Error implements the error interface.
func (v *Error) Error() string {
return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
func (e *Error) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.ErrorBody())
}
type OmitValueType struct{}
@ -48,21 +70,21 @@ var omitValue = OmitValueType{}
// ErrorBody returns the error message without the field name. This is useful
// for building nice-looking higher-level error reporting.
func (v *Error) ErrorBody() string {
func (e *Error) ErrorBody() string {
var s string
switch {
case v.Type == ErrorTypeRequired:
s = v.Type.String()
case v.Type == ErrorTypeForbidden:
s = v.Type.String()
case v.Type == ErrorTypeTooLong:
s = v.Type.String()
case v.Type == ErrorTypeInternal:
s = v.Type.String()
case v.BadValue == omitValue:
s = v.Type.String()
case e.Type == ErrorTypeRequired:
s = e.Type.String()
case e.Type == ErrorTypeForbidden:
s = e.Type.String()
case e.Type == ErrorTypeTooLong:
s = e.Type.String()
case e.Type == ErrorTypeInternal:
s = e.Type.String()
case e.BadValue == omitValue:
s = e.Type.String()
default:
value := v.BadValue
value := e.BadValue
valueType := reflect.TypeOf(value)
if value == nil || valueType == nil {
value = "null"
@ -76,26 +98,38 @@ func (v *Error) ErrorBody() string {
switch t := value.(type) {
case int64, int32, float64, float32, bool:
// use simple printer for simple types
s = fmt.Sprintf("%s: %v", v.Type, value)
s = fmt.Sprintf("%s: %v", e.Type, value)
case string:
s = fmt.Sprintf("%s: %q", v.Type, t)
s = fmt.Sprintf("%s: %q", e.Type, t)
case fmt.Stringer:
// anything that defines String() is better than raw struct
s = fmt.Sprintf("%s: %s", v.Type, t.String())
s = fmt.Sprintf("%s: %s", e.Type, t.String())
default:
// fallback to raw struct
// TODO: internal types have panic guards against json.Marshalling to prevent
// accidental use of internal types in external serialized form. For now, use
// %#v, although it would be better to show a more expressive output in the future
s = fmt.Sprintf("%s: %#v", v.Type, value)
s = fmt.Sprintf("%s: %#v", e.Type, value)
}
}
if len(v.Detail) != 0 {
s += fmt.Sprintf(": %s", v.Detail)
if len(e.Detail) != 0 {
s += fmt.Sprintf(": %s", e.Detail)
}
return s
}
// WithOrigin adds origin information to the FieldError
func (e *Error) WithOrigin(o string) *Error {
e.Origin = o
return e
}
// MarkCoveredByDeclarative marks the error as covered by declarative validation.
func (e *Error) MarkCoveredByDeclarative() *Error {
e.CoveredByDeclarative = true
return e
}
// ErrorType is a machine readable value providing more detail about why
// a field is invalid. These values are expected to match 1-1 with
// CauseType in api/types.go.
@ -169,32 +203,32 @@ func (t ErrorType) String() string {
// TypeInvalid returns a *Error indicating "type is invalid"
func TypeInvalid(field *Path, value interface{}, detail string) *Error {
return &Error{ErrorTypeTypeInvalid, field.String(), value, detail}
return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, "", false}
}
// NotFound returns a *Error indicating "value not found". This is
// used to report failure to find a requested value (e.g. looking up an ID).
func NotFound(field *Path, value interface{}) *Error {
return &Error{ErrorTypeNotFound, field.String(), value, ""}
return &Error{ErrorTypeNotFound, field.String(), value, "", "", false}
}
// Required returns a *Error indicating "value required". This is used
// to report required values that are not provided (e.g. empty strings, null
// values, or empty arrays).
func Required(field *Path, detail string) *Error {
return &Error{ErrorTypeRequired, field.String(), "", detail}
return &Error{ErrorTypeRequired, field.String(), "", detail, "", false}
}
// Duplicate returns a *Error indicating "duplicate value". This is
// used to report collisions of values that must be unique (e.g. names or IDs).
func Duplicate(field *Path, value interface{}) *Error {
return &Error{ErrorTypeDuplicate, field.String(), value, ""}
return &Error{ErrorTypeDuplicate, field.String(), value, "", "", false}
}
// Invalid returns a *Error indicating "invalid value". This is used
// to report malformed values (e.g. failed regex match, too long, out of bounds).
func Invalid(field *Path, value interface{}, detail string) *Error {
return &Error{ErrorTypeInvalid, field.String(), value, detail}
return &Error{ErrorTypeInvalid, field.String(), value, detail, "", false}
}
// NotSupported returns a *Error indicating "unsupported value".
@ -209,7 +243,7 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E
}
detail = "supported values: " + strings.Join(quotedValues, ", ")
}
return &Error{ErrorTypeNotSupported, field.String(), value, detail}
return &Error{ErrorTypeNotSupported, field.String(), value, detail, "", false}
}
// Forbidden returns a *Error indicating "forbidden". This is used to
@ -217,7 +251,7 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E
// some conditions, but which are not permitted by current conditions (e.g.
// security policy).
func Forbidden(field *Path, detail string) *Error {
return &Error{ErrorTypeForbidden, field.String(), "", detail}
return &Error{ErrorTypeForbidden, field.String(), "", detail, "", false}
}
// TooLong returns a *Error indicating "too long". This is used to report that
@ -231,7 +265,7 @@ func TooLong(field *Path, value interface{}, maxLength int) *Error {
} else {
msg = "value is too long"
}
return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg}
return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg, "", false}
}
// TooLongMaxLength returns a *Error indicating "too long".
@ -259,14 +293,14 @@ func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
actual = omitValue
}
return &Error{ErrorTypeTooMany, field.String(), actual, msg}
return &Error{ErrorTypeTooMany, field.String(), actual, msg, "", false}
}
// InternalError returns a *Error indicating "internal error". This is used
// to signal that an error was found that was not directly related to user
// input. The err argument must be non-nil.
func InternalError(field *Path, err error) *Error {
return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
return &Error{ErrorTypeInternal, field.String(), nil, err.Error(), "", false}
}
// ErrorList holds a set of Errors. It is plausible that we might one day have
@ -285,6 +319,22 @@ func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
}
}
// WithOrigin sets the origin for all errors in the list and returns the updated list.
func (list ErrorList) WithOrigin(origin string) ErrorList {
for _, err := range list {
err.Origin = origin
}
return list
}
// MarkCoveredByDeclarative marks all errors in the list as covered by declarative validation.
func (list ErrorList) MarkCoveredByDeclarative() ErrorList {
for _, err := range list {
err.CoveredByDeclarative = true
}
return list
}
// ToAggregate converts the ErrorList into an errors.Aggregate.
func (list ErrorList) ToAggregate() utilerrors.Aggregate {
if len(list) == 0 {
@ -321,3 +371,25 @@ func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
// FilterOut takes an Aggregate and returns an Aggregate
return fromAggregate(err.(utilerrors.Aggregate))
}
// ExtractCoveredByDeclarative returns a new ErrorList containing only the errors that should be covered by declarative validation.
func (list ErrorList) ExtractCoveredByDeclarative() ErrorList {
newList := ErrorList{}
for _, err := range list {
if err.CoveredByDeclarative {
newList = append(newList, err)
}
}
return newList
}
// RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation.
func (list ErrorList) RemoveCoveredByDeclarative() ErrorList {
newList := ErrorList{}
for _, err := range list {
if !err.CoveredByDeclarative {
newList = append(newList, err)
}
}
return newList
}

@ -0,0 +1,278 @@
/*
Copyright 2023 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 validation
import (
"fmt"
"net"
"net/netip"
"slices"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/klog/v2"
netutils "k8s.io/utils/net"
)
func parseIP(fldPath *field.Path, value string, strictValidation bool) (net.IP, field.ErrorList) {
var allErrors field.ErrorList
ip := netutils.ParseIPSloppy(value)
if ip == nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)"))
return nil, allErrors
}
if strictValidation {
addr, err := netip.ParseAddr(value)
if err != nil {
// If netutils.ParseIPSloppy parsed it, but netip.ParseAddr
// doesn't, then it must have illegal leading 0s.
allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have leading 0s"))
}
if addr.Is4In6() {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must not be an IPv4-mapped IPv6 address"))
}
}
return ip, allErrors
}
// IsValidIPForLegacyField tests that the argument is a valid IP address for a "legacy"
// API field that predates strict IP validation. In particular, this allows IPs that are
// not in canonical form (e.g., "FE80:0:0:0:0:0:0:0abc" instead of "fe80::abc").
//
// If strictValidation is false, this also allows IPs in certain invalid or ambiguous
// formats:
//
// 1. IPv4 IPs are allowed to have leading "0"s in octets (e.g. "010.002.003.004").
// Historically, net.ParseIP (and later netutils.ParseIPSloppy) simply ignored leading
// "0"s in IPv4 addresses, but most libc-based software treats 0-prefixed IPv4 octets
// as octal, meaning different software might interpret the same string as two
// different IPs, potentially leading to security issues. (Current net.ParseIP and
// netip.ParseAddr simply reject inputs with leading "0"s.)
//
// 2. IPv4-mapped IPv6 IPs (e.g. "::ffff:1.2.3.4") are allowed. These can also lead to
// different software interpreting the value in different ways, because they may be
// treated as IPv4 by some software and IPv6 by other software. (net.ParseIP and
// netip.ParseAddr both allow these, but there are no use cases for representing IPv4
// addresses as IPv4-mapped IPv6 addresses in Kubernetes.)
//
// Alternatively, when validating an update to an existing field, you can pass a list of
// IP values from the old object that should be accepted if they appear in the new object
// even if they are not valid.
//
// This function should only be used to validate the existing fields that were
// historically validated in this way, and strictValidation should be true unless the
// StrictIPCIDRValidation feature gate is disabled. Use IsValidIP for parsing new fields.
func IsValidIPForLegacyField(fldPath *field.Path, value string, strictValidation bool, validOldIPs []string) field.ErrorList {
if slices.Contains(validOldIPs, value) {
return nil
}
_, allErrors := parseIP(fldPath, value, strictValidation)
return allErrors.WithOrigin("format=ip-sloppy")
}
// IsValidIP tests that the argument is a valid IP address, according to current
// Kubernetes standards for IP address validation.
func IsValidIP(fldPath *field.Path, value string) field.ErrorList {
ip, allErrors := parseIP(fldPath, value, true)
if len(allErrors) != 0 {
return allErrors.WithOrigin("format=ip-strict")
}
if value != ip.String() {
allErrors = append(allErrors, field.Invalid(fldPath, value, fmt.Sprintf("must be in canonical form (%q)", ip.String())))
}
return allErrors.WithOrigin("format=ip-strict")
}
// GetWarningsForIP returns warnings for IP address values in non-standard forms. This
// should only be used with fields that are validated with IsValidIPForLegacyField().
func GetWarningsForIP(fldPath *field.Path, value string) []string {
ip := netutils.ParseIPSloppy(value)
if ip == nil {
klog.ErrorS(nil, "GetWarningsForIP called on value that was not validated with IsValidIPForLegacyField", "field", fldPath, "value", value)
return nil
}
addr, _ := netip.ParseAddr(value)
if !addr.IsValid() || addr.Is4In6() {
// This catches 2 cases: leading 0s (if ParseIPSloppy() accepted it but
// ParseAddr() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way,
// re-stringifying the net.IP value will give the preferred form.
return []string{
fmt.Sprintf("%s: non-standard IP address %q will be considered invalid in a future Kubernetes release: use %q", fldPath, value, ip.String()),
}
}
// If ParseIPSloppy() and ParseAddr() both accept it then it's fully valid, though
// it may be non-canonical.
if addr.Is6() && addr.String() != value {
return []string{
fmt.Sprintf("%s: IPv6 address %q should be in RFC 5952 canonical format (%q)", fldPath, value, addr.String()),
}
}
return nil
}
func parseCIDR(fldPath *field.Path, value string, strictValidation bool) (*net.IPNet, field.ErrorList) {
var allErrors field.ErrorList
_, ipnet, err := netutils.ParseCIDRSloppy(value)
if err != nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid CIDR value, (e.g. 10.9.8.0/24 or 2001:db8::/64)"))
return nil, allErrors
}
if strictValidation {
prefix, err := netip.ParsePrefix(value)
if err != nil {
// If netutils.ParseCIDRSloppy parsed it, but netip.ParsePrefix
// doesn't, then it must have illegal leading 0s (either in the
// IP part or the prefix).
allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have leading 0s in IP or prefix length"))
} else if prefix.Addr().Is4In6() {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have an IPv4-mapped IPv6 address"))
} else if prefix.Addr() != prefix.Masked().Addr() {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have bits set beyond the prefix length"))
}
}
return ipnet, allErrors
}
// IsValidCIDRForLegacyField tests that the argument is a valid CIDR value for a "legacy"
// API field that predates strict IP validation. In particular, this allows IPs that are
// not in canonical form (e.g., "FE80:0abc:0:0:0:0:0:0/64" instead of "fe80:abc::/64").
//
// If strictValidation is false, this also allows CIDR values in certain invalid or
// ambiguous formats:
//
// 1. The IP part of the CIDR value is parsed as with IsValidIPForLegacyField with
// strictValidation=false.
//
// 2. The CIDR value is allowed to be either a "subnet"/"mask" (with the lower bits after
// the prefix length all being 0), or an "interface address" as with `ip addr` (with a
// complete IP address and associated subnet length). With strict validation, the
// value is required to be in "subnet"/"mask" form.
//
// 3. The prefix length is allowed to have leading 0s.
//
// Alternatively, when validating an update to an existing field, you can pass a list of
// CIDR values from the old object that should be accepted if they appear in the new
// object even if they are not valid.
//
// This function should only be used to validate the existing fields that were
// historically validated in this way, and strictValidation should be true unless the
// StrictIPCIDRValidation feature gate is disabled. Use IsValidCIDR or
// IsValidInterfaceAddress for parsing new fields.
func IsValidCIDRForLegacyField(fldPath *field.Path, value string, strictValidation bool, validOldCIDRs []string) field.ErrorList {
if slices.Contains(validOldCIDRs, value) {
return nil
}
_, allErrors := parseCIDR(fldPath, value, strictValidation)
return allErrors
}
// IsValidCIDR tests that the argument is a valid CIDR value, according to current
// Kubernetes standards for CIDR validation. This function is only for
// "subnet"/"mask"-style CIDR values (e.g., "192.168.1.0/24", with no bits set beyond the
// prefix length). Use IsValidInterfaceAddress for "ifaddr"-style CIDR values.
func IsValidCIDR(fldPath *field.Path, value string) field.ErrorList {
ipnet, allErrors := parseCIDR(fldPath, value, true)
if len(allErrors) != 0 {
return allErrors
}
if value != ipnet.String() {
allErrors = append(allErrors, field.Invalid(fldPath, value, fmt.Sprintf("must be in canonical form (%q)", ipnet.String())))
}
return allErrors
}
// GetWarningsForCIDR returns warnings for CIDR values in non-standard forms. This should
// only be used with fields that are validated with IsValidCIDRForLegacyField().
func GetWarningsForCIDR(fldPath *field.Path, value string) []string {
ip, ipnet, err := netutils.ParseCIDRSloppy(value)
if err != nil {
klog.ErrorS(err, "GetWarningsForCIDR called on value that was not validated with IsValidCIDRForLegacyField", "field", fldPath, "value", value)
return nil
}
var warnings []string
// Check for bits set after prefix length
if !ip.Equal(ipnet.IP) {
_, addrlen := ipnet.Mask.Size()
singleIPCIDR := fmt.Sprintf("%s/%d", ip.String(), addrlen)
warnings = append(warnings,
fmt.Sprintf("%s: CIDR value %q is ambiguous in this context (should be %q or %q?)", fldPath, value, ipnet.String(), singleIPCIDR),
)
}
prefix, _ := netip.ParsePrefix(value)
addr := prefix.Addr()
if !prefix.IsValid() || addr.Is4In6() {
// This catches 2 cases: leading 0s (if ParseCIDRSloppy() accepted it but
// ParsePrefix() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way,
// re-stringifying the net.IPNet value will give the preferred form.
warnings = append(warnings,
fmt.Sprintf("%s: non-standard CIDR value %q will be considered invalid in a future Kubernetes release: use %q", fldPath, value, ipnet.String()),
)
}
// If ParseCIDRSloppy() and ParsePrefix() both accept it then it's fully valid,
// though it may be non-canonical. But only check this if there are no other
// warnings, since either of the other warnings would also cause a round-trip
// failure.
if len(warnings) == 0 && addr.Is6() && prefix.String() != value {
warnings = append(warnings,
fmt.Sprintf("%s: IPv6 CIDR value %q should be in RFC 5952 canonical format (%q)", fldPath, value, prefix.String()),
)
}
return warnings
}
// IsValidInterfaceAddress tests that the argument is a valid "ifaddr"-style CIDR value in
// canonical form (e.g., "192.168.1.5/24", with a complete IP address and associated
// subnet length). Use IsValidCIDR for "subnet"/"mask"-style CIDR values (e.g.,
// "192.168.1.0/24").
func IsValidInterfaceAddress(fldPath *field.Path, value string) field.ErrorList {
var allErrors field.ErrorList
ip, ipnet, err := netutils.ParseCIDRSloppy(value)
if err != nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid address in CIDR form, (e.g. 10.9.8.7/24 or 2001:db8::1/64)"))
return allErrors
}
// The canonical form of `value` is not `ipnet.String()`, because `ipnet` doesn't
// include the bits after the prefix. We need to construct the canonical form
// ourselves from `ip` and `ipnet.Mask`.
maskSize, _ := ipnet.Mask.Size()
if netutils.IsIPv4(ip) && maskSize > net.IPv4len*8 {
// "::ffff:192.168.0.1/120" -> "192.168.0.1/24"
maskSize -= (net.IPv6len - net.IPv4len) * 8
}
canonical := fmt.Sprintf("%s/%d", ip.String(), maskSize)
if value != canonical {
allErrors = append(allErrors, field.Invalid(fldPath, value, fmt.Sprintf("must be in canonical form (%q)", canonical)))
}
return allErrors
}

@ -24,7 +24,6 @@ import (
"unicode"
"k8s.io/apimachinery/pkg/util/validation/field"
netutils "k8s.io/utils/net"
)
const qnameCharFmt string = "[A-Za-z0-9]"
@ -369,45 +368,6 @@ func IsValidPortName(port string) []string {
return errs
}
// IsValidIP tests that the argument is a valid IP address.
func IsValidIP(fldPath *field.Path, value string) field.ErrorList {
var allErrors field.ErrorList
if netutils.ParseIPSloppy(value) == nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)"))
}
return allErrors
}
// IsValidIPv4Address tests that the argument is a valid IPv4 address.
func IsValidIPv4Address(fldPath *field.Path, value string) field.ErrorList {
var allErrors field.ErrorList
ip := netutils.ParseIPSloppy(value)
if ip == nil || ip.To4() == nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv4 address"))
}
return allErrors
}
// IsValidIPv6Address tests that the argument is a valid IPv6 address.
func IsValidIPv6Address(fldPath *field.Path, value string) field.ErrorList {
var allErrors field.ErrorList
ip := netutils.ParseIPSloppy(value)
if ip == nil || ip.To4() != nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv6 address"))
}
return allErrors
}
// IsValidCIDR tests that the argument is a valid CIDR value.
func IsValidCIDR(fldPath *field.Path, value string) field.ErrorList {
var allErrors field.ErrorList
_, _, err := netutils.ParseCIDRSloppy(value)
if err != nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid CIDR value, (e.g. 10.9.8.0/24 or 2001:db8::/64)"))
}
return allErrors
}
const percentFmt string = "[0-9]+%"
const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'"

@ -157,6 +157,8 @@ func (b Backoff) DelayWithReset(c clock.Clock, resetInterval time.Duration) Dela
// Until is syntactic sugar on top of JitterUntil with zero jitter factor and
// with sliding = true (which means the timer for period starts after the f
// completes).
//
// Contextual logging: UntilWithContext should be used instead of Until in code which supports contextual logging.
func Until(f func(), period time.Duration, stopCh <-chan struct{}) {
JitterUntil(f, period, 0.0, true, stopCh)
}
@ -176,6 +178,8 @@ func UntilWithContext(ctx context.Context, f func(context.Context), period time.
// NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter
// factor, with sliding = false (meaning the timer for period starts at the same
// time as the function starts).
//
// Contextual logging: NonSlidingUntilWithContext should be used instead of NonSlidingUntil in code which supports contextual logging.
func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) {
JitterUntil(f, period, 0.0, false, stopCh)
}
@ -200,19 +204,44 @@ func NonSlidingUntilWithContext(ctx context.Context, f func(context.Context), pe
//
// Close stopCh to stop. f may not be invoked if stop channel is already
// closed. Pass NeverStop to if you don't want it stop.
//
// Contextual logging: JitterUntilWithContext should be used instead of JitterUntil in code which supports contextual logging.
func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) {
BackoffUntil(f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding, stopCh)
}
// JitterUntilWithContext loops until context is done, running f every period.
//
// If jitterFactor is positive, the period is jittered before every run of f.
// If jitterFactor is not positive, the period is unchanged and not jittered.
//
// If sliding is true, the period is computed after f runs. If it is false then
// period includes the runtime for f.
//
// Cancel context to stop. f may not be invoked if context is already done.
func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) {
BackoffUntilWithContext(ctx, f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding)
}
// BackoffUntil loops until stop channel is closed, run f every duration given by BackoffManager.
//
// If sliding is true, the period is computed after f runs. If it is false then
// period includes the runtime for f.
//
// Contextual logging: BackoffUntilWithContext should be used instead of BackoffUntil in code which supports contextual logging.
func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan struct{}) {
BackoffUntilWithContext(ContextForChannel(stopCh), func(context.Context) { f() }, backoff, sliding)
}
// BackoffUntilWithContext loops until context is done, run f every duration given by BackoffManager.
//
// If sliding is true, the period is computed after f runs. If it is false then
// period includes the runtime for f.
func BackoffUntilWithContext(ctx context.Context, f func(ctx context.Context), backoff BackoffManager, sliding bool) {
var t clock.Timer
for {
select {
case <-stopCh:
case <-ctx.Done():
return
default:
}
@ -222,8 +251,8 @@ func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan
}
func() {
defer runtime.HandleCrash()
f()
defer runtime.HandleCrashWithContext(ctx)
f(ctx)
}()
if sliding {
@ -236,7 +265,7 @@ func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan
// In order to mitigate we re-check stopCh at the beginning
// of every loop to prevent extra executions of f().
select {
case <-stopCh:
case <-ctx.Done():
if !t.Stop() {
<-t.C()
}
@ -246,19 +275,6 @@ func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan
}
}
// JitterUntilWithContext loops until context is done, running f every period.
//
// If jitterFactor is positive, the period is jittered before every run of f.
// If jitterFactor is not positive, the period is unchanged and not jittered.
//
// If sliding is true, the period is computed after f runs. If it is false then
// period includes the runtime for f.
//
// Cancel context to stop. f may not be invoked if context is already expired.
func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) {
JitterUntil(func() { f(ctx) }, period, jitterFactor, sliding, ctx.Done())
}
// backoffManager provides simple backoff behavior in a threadsafe manner to a caller.
type backoffManager struct {
backoff Backoff

@ -16,4 +16,4 @@ limitations under the License.
// Package wait provides tools for polling or listening for changes
// to a condition.
package wait // import "k8s.io/apimachinery/pkg/util/wait"
package wait

@ -49,7 +49,7 @@ func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding
// if we haven't requested immediate execution, delay once
if immediate {
if ok, err := func() (bool, error) {
defer runtime.HandleCrash()
defer runtime.HandleCrashWithContext(ctx)
return condition(ctx)
}(); err != nil || ok {
return err
@ -83,7 +83,7 @@ func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding
t.Next()
}
if ok, err := func() (bool, error) {
defer runtime.HandleCrash()
defer runtime.HandleCrashWithContext(ctx)
return condition(ctx)
}(); err != nil || ok {
return err

@ -80,6 +80,10 @@ func Forever(f func(), period time.Duration) {
Until(f, period, NeverStop)
}
// jitterRand is a dedicated random source for jitter calculations.
// It defaults to rand.Float64, but is a package variable so it can be overridden to make unit tests deterministic.
var jitterRand = rand.Float64
// Jitter returns a time.Duration between duration and duration + maxFactor *
// duration.
//
@ -89,7 +93,7 @@ func Jitter(duration time.Duration, maxFactor float64) time.Duration {
if maxFactor <= 0.0 {
maxFactor = 1.0
}
wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration))
wait := duration + time.Duration(jitterRand()*maxFactor*float64(duration))
return wait
}
@ -141,6 +145,7 @@ func (c channelContext) Value(key any) any { return nil }
//
// Deprecated: Will be removed when the legacy polling methods are removed.
func runConditionWithCrashProtection(condition ConditionFunc) (bool, error) {
//nolint:logcheck // Already deprecated.
defer runtime.HandleCrash()
return condition()
}
@ -150,7 +155,7 @@ func runConditionWithCrashProtection(condition ConditionFunc) (bool, error) {
//
// Deprecated: Will be removed when the legacy polling methods are removed.
func runConditionWithCrashProtectionWithContext(ctx context.Context, condition ConditionWithContextFunc) (bool, error) {
defer runtime.HandleCrash()
defer runtime.HandleCrashWithContext(ctx)
return condition(ctx)
}

@ -20,10 +20,12 @@ import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"unicode"
"unicode/utf8"
jsonutil "k8s.io/apimachinery/pkg/util/json"
@ -92,7 +94,7 @@ func UnmarshalStrict(data []byte, v interface{}) error {
// YAML decoding path is not used (so that error messages are
// JSON specific).
func ToJSON(data []byte) ([]byte, error) {
if hasJSONPrefix(data) {
if IsJSONBuffer(data) {
return data, nil
}
return yaml.YAMLToJSON(data)
@ -102,7 +104,8 @@ func ToJSON(data []byte) ([]byte, error) {
// separating individual documents. It first converts the YAML
// body to JSON, then unmarshals the JSON.
type YAMLToJSONDecoder struct {
reader Reader
reader Reader
inputOffset int
}
// NewYAMLToJSONDecoder decodes YAML documents from the provided
@ -121,7 +124,7 @@ func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder {
// yaml.Unmarshal.
func (d *YAMLToJSONDecoder) Decode(into interface{}) error {
bytes, err := d.reader.Read()
if err != nil && err != io.EOF {
if err != nil && err != io.EOF { //nolint:errorlint
return err
}
@ -131,9 +134,14 @@ func (d *YAMLToJSONDecoder) Decode(into interface{}) error {
return YAMLSyntaxError{err}
}
}
d.inputOffset += len(bytes)
return err
}
func (d *YAMLToJSONDecoder) InputOffset() int {
return d.inputOffset
}
// YAMLDecoder reads chunks of objects and returns ErrShortBuffer if
// the data is not sufficient.
type YAMLDecoder struct {
@ -229,18 +237,20 @@ func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err
return 0, nil, nil
}
// decoder is a convenience interface for Decode.
type decoder interface {
Decode(into interface{}) error
}
// YAMLOrJSONDecoder attempts to decode a stream of JSON documents or
// YAML documents by sniffing for a leading { character.
// YAMLOrJSONDecoder attempts to decode a stream of JSON or YAML documents.
// While JSON is YAML, the way Go's JSON decode defines a multi-document stream
// is a series of JSON objects (e.g. {}{}), but YAML defines a multi-document
// stream as a series of documents separated by "---".
//
// This decoder will attempt to decode the stream as JSON first, and if that
// fails, it will switch to YAML. Once it determines the stream is JSON (by
// finding a non-YAML-delimited series of objects), it will not switch to YAML.
// Once it switches to YAML it will not switch back to JSON.
type YAMLOrJSONDecoder struct {
r io.Reader
bufferSize int
decoder decoder
json *json.Decoder
yaml *YAMLToJSONDecoder
stream *StreamReader
count int // how many objects have been decoded
}
type JSONSyntaxError struct {
@ -265,31 +275,108 @@ func (e YAMLSyntaxError) Error() string {
// how far into the stream the decoder will look to figure out whether this
// is a JSON stream (has whitespace followed by an open brace).
func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder {
return &YAMLOrJSONDecoder{
r: r,
bufferSize: bufferSize,
d := &YAMLOrJSONDecoder{}
reader, _, mightBeJSON := GuessJSONStream(r, bufferSize)
d.stream = reader
if mightBeJSON {
d.json = json.NewDecoder(reader)
} else {
d.yaml = NewYAMLToJSONDecoder(reader)
}
return d
}
// Decode unmarshals the next object from the underlying stream into the
// provide object, or returns an error.
func (d *YAMLOrJSONDecoder) Decode(into interface{}) error {
if d.decoder == nil {
buffer, _, isJSON := GuessJSONStream(d.r, d.bufferSize)
if isJSON {
d.decoder = json.NewDecoder(buffer)
// Because we don't know if this is a JSON or YAML stream, a failure from
// both decoders is ambiguous. When in doubt, it will return the error from
// the JSON decoder. Unfortunately, this means that if the first document
// is invalid YAML, the error won't be awesome.
// TODO: the errors from YAML are not great, we could improve them a lot.
var firstErr error
if d.json != nil {
err := d.json.Decode(into)
if err == nil {
d.stream.Consume(int(d.json.InputOffset()) - d.stream.Consumed())
d.count++
return nil
}
if err == io.EOF { //nolint:errorlint
return err
}
var syntax *json.SyntaxError
if ok := errors.As(err, &syntax); ok {
firstErr = JSONSyntaxError{
Offset: syntax.Offset,
Err: syntax,
}
} else {
d.decoder = NewYAMLToJSONDecoder(buffer)
firstErr = err
}
if d.count > 1 {
// If we found 0 or 1 JSON object(s), this stream is still
// ambiguous. But if we found more than 1 JSON object, then this
// is an unambiguous JSON stream, and we should not switch to YAML.
return err
}
// If JSON decoding hits the end of one object and then fails on the
// next, it leaves any leading whitespace in the buffer, which can
// confuse the YAML decoder. We just eat any whitespace we find, up to
// and including the first newline.
d.stream.Rewind()
if err := d.consumeWhitespace(); err == nil {
d.yaml = NewYAMLToJSONDecoder(d.stream)
}
d.json = nil
}
if d.yaml != nil {
err := d.yaml.Decode(into)
if err == nil {
d.stream.Consume(d.yaml.InputOffset() - d.stream.Consumed())
d.count++
return nil
}
if err == io.EOF { //nolint:errorlint
return err
}
if firstErr == nil {
firstErr = err
}
}
err := d.decoder.Decode(into)
if syntax, ok := err.(*json.SyntaxError); ok {
return JSONSyntaxError{
Offset: syntax.Offset,
Err: syntax,
if firstErr != nil {
return firstErr
}
return fmt.Errorf("decoding failed as both JSON and YAML")
}
func (d *YAMLOrJSONDecoder) consumeWhitespace() error {
consumed := 0
for {
buf, err := d.stream.ReadN(4)
if err != nil && err == io.EOF { //nolint:errorlint
return err
}
r, sz := utf8.DecodeRune(buf)
if r == utf8.RuneError || sz == 0 {
return fmt.Errorf("invalid utf8 rune")
}
d.stream.RewindN(len(buf) - sz)
if !unicode.IsSpace(r) {
d.stream.RewindN(sz)
d.stream.Consume(consumed)
return nil
}
if r == '\n' {
d.stream.Consume(consumed)
return nil
}
if err == io.EOF { //nolint:errorlint
break
}
}
return err
return io.EOF
}
type Reader interface {
@ -311,7 +398,7 @@ func (r *YAMLReader) Read() ([]byte, error) {
var buffer bytes.Buffer
for {
line, err := r.reader.Read()
if err != nil && err != io.EOF {
if err != nil && err != io.EOF { //nolint:errorlint
return nil, err
}
@ -329,11 +416,11 @@ func (r *YAMLReader) Read() ([]byte, error) {
if buffer.Len() != 0 {
return buffer.Bytes(), nil
}
if err == io.EOF {
if err == io.EOF { //nolint:errorlint
return nil, err
}
}
if err == io.EOF {
if err == io.EOF { //nolint:errorlint
if buffer.Len() != 0 {
// If we're at EOF, we have a final, non-terminated line. Return it.
return buffer.Bytes(), nil
@ -369,26 +456,20 @@ func (r *LineReader) Read() ([]byte, error) {
// GuessJSONStream scans the provided reader up to size, looking
// for an open brace indicating this is JSON. It will return the
// bufio.Reader it creates for the consumer.
func GuessJSONStream(r io.Reader, size int) (io.Reader, []byte, bool) {
buffer := bufio.NewReaderSize(r, size)
func GuessJSONStream(r io.Reader, size int) (*StreamReader, []byte, bool) {
buffer := NewStreamReader(r, size)
b, _ := buffer.Peek(size)
return buffer, b, hasJSONPrefix(b)
return buffer, b, IsJSONBuffer(b)
}
// IsJSONBuffer scans the provided buffer, looking
// for an open brace indicating this is JSON.
func IsJSONBuffer(buf []byte) bool {
return hasJSONPrefix(buf)
return hasPrefix(buf, jsonPrefix)
}
var jsonPrefix = []byte("{")
// hasJSONPrefix returns true if the provided buffer appears to start with
// a JSON open brace.
func hasJSONPrefix(buf []byte) bool {
return hasPrefix(buf, jsonPrefix)
}
// Return true if the first non-whitespace bytes in buf is
// prefix.
func hasPrefix(buf []byte, prefix []byte) bool {

@ -0,0 +1,130 @@
/*
Copyright 2025 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 yaml
import "io"
// StreamReader is a reader designed for consuming streams of variable-length
// messages. It buffers data until it is explicitly consumed, and can be
// rewound to re-read previous data.
type StreamReader struct {
r io.Reader
buf []byte
head int // current read offset into buf
ttlConsumed int // number of bytes which have been consumed
}
// NewStreamReader creates a new StreamReader wrapping the provided
// io.Reader.
func NewStreamReader(r io.Reader, size int) *StreamReader {
if size == 0 {
size = 4096
}
return &StreamReader{
r: r,
buf: make([]byte, 0, size), // Start with a reasonable capacity
}
}
// Read implements io.Reader. It first returns any buffered data after the
// current offset, and if that's exhausted, reads from the underlying reader
// and buffers the data. The returned data is not considered consumed until the
// Consume method is called.
func (r *StreamReader) Read(p []byte) (n int, err error) {
// If we have buffered data, return it
if r.head < len(r.buf) {
n = copy(p, r.buf[r.head:])
r.head += n
return n, nil
}
// If we've already hit EOF, return it
if r.r == nil {
return 0, io.EOF
}
// Read from the underlying reader
n, err = r.r.Read(p)
if n > 0 {
r.buf = append(r.buf, p[:n]...)
r.head += n
}
if err == nil {
return n, nil
}
if err == io.EOF {
// Store that we've hit EOF by setting r to nil
r.r = nil
}
return n, err
}
// ReadN reads exactly n bytes from the reader, blocking until all bytes are
// read or an error occurs. If an error occurs, the number of bytes read is
// returned along with the error. If EOF is hit before n bytes are read, this
// will return the bytes read so far, along with io.EOF. The returned data is
// not considered consumed until the Consume method is called.
func (r *StreamReader) ReadN(want int) ([]byte, error) {
ret := make([]byte, want)
off := 0
for off < want {
n, err := r.Read(ret[off:])
if err != nil {
return ret[:off+n], err
}
off += n
}
return ret, nil
}
// Peek returns the next n bytes without advancing the reader. The returned
// bytes are valid until the next call to Consume.
func (r *StreamReader) Peek(n int) ([]byte, error) {
buf, err := r.ReadN(n)
r.RewindN(len(buf))
if err != nil {
return buf, err
}
return buf, nil
}
// Rewind resets the reader to the beginning of the buffered data.
func (r *StreamReader) Rewind() {
r.head = 0
}
// RewindN rewinds the reader by n bytes. If n is greater than the current
// buffer, the reader is rewound to the beginning of the buffer.
func (r *StreamReader) RewindN(n int) {
r.head -= min(n, r.head)
}
// Consume discards up to n bytes of previously read data from the beginning of
// the buffer. Once consumed, that data is no longer available for rewinding.
// If n is greater than the current buffer, the buffer is cleared. Consume
// never consume data from the underlying reader.
func (r *StreamReader) Consume(n int) {
n = min(n, len(r.buf))
r.buf = r.buf[n:]
r.head -= n
r.ttlConsumed += n
}
// Consumed returns the number of bytes consumed from the input reader.
func (r *StreamReader) Consumed() int {
return r.ttlConsumed
}

@ -16,5 +16,5 @@ limitations under the License.
// +k8s:openapi-gen=true
// Package version supplies the type for version information collected at build time.
package version // import "k8s.io/apimachinery/pkg/version"
// Package version supplies the type for version information.
package version

@ -20,15 +20,25 @@ package version
// TODO: Add []string of api versions supported? It's still unclear
// how we'll want to distribute that information.
type Info struct {
Major string `json:"major"`
Minor string `json:"minor"`
GitVersion string `json:"gitVersion"`
GitCommit string `json:"gitCommit"`
GitTreeState string `json:"gitTreeState"`
BuildDate string `json:"buildDate"`
GoVersion string `json:"goVersion"`
Compiler string `json:"compiler"`
Platform string `json:"platform"`
// Major is the major version of the binary version
Major string `json:"major"`
// Minor is the minor version of the binary version
Minor string `json:"minor"`
// EmulationMajor is the major version of the emulation version
EmulationMajor string `json:"emulationMajor,omitempty"`
// EmulationMinor is the minor version of the emulation version
EmulationMinor string `json:"emulationMinor,omitempty"`
// MinCompatibilityMajor is the major version of the minimum compatibility version
MinCompatibilityMajor string `json:"minCompatibilityMajor,omitempty"`
// MinCompatibilityMinor is the minor version of the minimum compatibility version
MinCompatibilityMinor string `json:"minCompatibilityMinor,omitempty"`
GitVersion string `json:"gitVersion"`
GitCommit string `json:"gitCommit"`
GitTreeState string `json:"gitTreeState"`
BuildDate string `json:"buildDate"`
GoVersion string `json:"goVersion"`
Compiler string `json:"compiler"`
Platform string `json:"platform"`
}
// String returns info as a human-friendly version string.

@ -16,4 +16,4 @@ limitations under the License.
// Package watch contains a generic watchable interface, and a fake for
// testing code that uses the watch interface.
package watch // import "k8s.io/apimachinery/pkg/watch"
package watch

@ -51,6 +51,7 @@ type Reporter interface {
// StreamWatcher turns any stream for which you can write a Decoder interface
// into a watch.Interface.
type StreamWatcher struct {
logger klog.Logger
sync.Mutex
source Decoder
reporter Reporter
@ -59,8 +60,16 @@ type StreamWatcher struct {
}
// NewStreamWatcher creates a StreamWatcher from the given decoder.
//
// Contextual logging: NewStreamWatcherWithLogger should be used instead of NewStreamWatcher in code which supports contextual logging.
func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher {
return NewStreamWatcherWithLogger(klog.Background(), d, r)
}
// NewStreamWatcherWithLogger creates a StreamWatcher from the given decoder and logger.
func NewStreamWatcherWithLogger(logger klog.Logger, d Decoder, r Reporter) *StreamWatcher {
sw := &StreamWatcher{
logger: logger,
source: d,
reporter: r,
// It's easy for a consumer to add buffering via an extra
@ -98,7 +107,7 @@ func (sw *StreamWatcher) Stop() {
// receive reads result from the decoder in a loop and sends down the result channel.
func (sw *StreamWatcher) receive() {
defer utilruntime.HandleCrash()
defer utilruntime.HandleCrashWithLogger(sw.logger)
defer close(sw.result)
defer sw.Stop()
for {
@ -108,10 +117,10 @@ func (sw *StreamWatcher) receive() {
case io.EOF:
// watch closed normally
case io.ErrUnexpectedEOF:
klog.V(1).Infof("Unexpected EOF during watch stream event decoding: %v", err)
sw.logger.V(1).Info("Unexpected EOF during watch stream event decoding", "err", err)
default:
if net.IsProbableEOF(err) || net.IsTimeout(err) {
klog.V(5).Infof("Unable to decode an event from the watch stream: %v", err)
sw.logger.V(5).Info("Unable to decode an event from the watch stream", "err", err)
} else {
select {
case <-sw.done:

@ -23,6 +23,7 @@ import (
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
)
// Interface can be implemented by anything that knows how to watch and report changes.
@ -103,29 +104,42 @@ func (w emptyWatch) ResultChan() <-chan Event {
// FakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
type FakeWatcher struct {
logger klog.Logger
result chan Event
stopped bool
sync.Mutex
}
var _ Interface = &FakeWatcher{}
// Contextual logging: NewFakeWithOptions and a logger in the FakeOptions should be used instead in code which supports contextual logging.
func NewFake() *FakeWatcher {
return &FakeWatcher{
result: make(chan Event),
}
return NewFakeWithOptions(FakeOptions{})
}
// Contextual logging: NewFakeWithOptions and a logger in the FakeOptions should be used instead in code which supports contextual logging.
func NewFakeWithChanSize(size int, blocking bool) *FakeWatcher {
return NewFakeWithOptions(FakeOptions{ChannelSize: size})
}
func NewFakeWithOptions(options FakeOptions) *FakeWatcher {
return &FakeWatcher{
result: make(chan Event, size),
logger: ptr.Deref(options.Logger, klog.Background()),
result: make(chan Event, options.ChannelSize),
}
}
type FakeOptions struct {
Logger *klog.Logger
ChannelSize int
}
// Stop implements Interface.Stop().
func (f *FakeWatcher) Stop() {
f.Lock()
defer f.Unlock()
if !f.stopped {
klog.V(4).Infof("Stopping fake watcher.")
f.logger.V(4).Info("Stopping fake watcher")
close(f.result)
f.stopped = true
}
@ -176,13 +190,22 @@ func (f *FakeWatcher) Action(action EventType, obj runtime.Object) {
// RaceFreeFakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
type RaceFreeFakeWatcher struct {
logger klog.Logger
result chan Event
Stopped bool
sync.Mutex
}
var _ Interface = &RaceFreeFakeWatcher{}
// Contextual logging: RaceFreeFakeWatcherWithLogger should be used instead of NewRaceFreeFake in code which supports contextual logging.
func NewRaceFreeFake() *RaceFreeFakeWatcher {
return NewRaceFreeFakeWithLogger(klog.Background())
}
func NewRaceFreeFakeWithLogger(logger klog.Logger) *RaceFreeFakeWatcher {
return &RaceFreeFakeWatcher{
logger: logger,
result: make(chan Event, DefaultChanSize),
}
}
@ -192,7 +215,7 @@ func (f *RaceFreeFakeWatcher) Stop() {
f.Lock()
defer f.Unlock()
if !f.Stopped {
klog.V(4).Infof("Stopping fake watcher.")
f.logger.V(4).Info("Stopping fake watcher")
close(f.result)
f.Stopped = true
}

@ -29,10 +29,10 @@ import (
"sync"
"time"
"github.com/golang/protobuf/proto"
openapi_v3 "github.com/google/gnostic-models/openapiv3"
"github.com/google/uuid"
"github.com/munnerz/goautoneg"
"google.golang.org/protobuf/proto"
"k8s.io/klog/v2"
"k8s.io/kube-openapi/pkg/cached"

@ -4,7 +4,7 @@ import (
"math/rand"
"strings"
fuzz "github.com/google/gofuzz"
"sigs.k8s.io/randfill"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@ -25,15 +25,15 @@ func randAlphanumString() string {
}
var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
func(s *string, c fuzz.Continue) {
func(s *string, c randfill.Continue) {
// All OpenAPI V3 map keys must follow the corresponding
// regex. Note that this restricts the range for all other
// string values as well.
str := randAlphanumString()
*s = str
},
func(o *OpenAPI, c fuzz.Continue) {
c.FuzzNoCustom(o)
func(o *OpenAPI, c randfill.Continue) {
c.FillNoCustom(o)
o.Version = "3.0.0"
for i, val := range o.SecurityRequirement {
if val == nil {
@ -48,45 +48,45 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
}
},
func(r *interface{}, c fuzz.Continue) {
func(r *interface{}, c randfill.Continue) {
switch c.Intn(3) {
case 0:
*r = nil
case 1:
n := c.RandString() + "x"
n := c.String(0) + "x"
*r = n
case 2:
n := c.Float64()
*r = n
}
},
func(v **spec.Info, c fuzz.Continue) {
func(v **spec.Info, c randfill.Continue) {
// Info is never nil
*v = &spec.Info{}
c.FuzzNoCustom(*v)
(*v).Title = c.RandString() + "x"
c.FillNoCustom(*v)
(*v).Title = c.String(0) + "x"
},
func(v *Paths, c fuzz.Continue) {
c.Fuzz(&v.VendorExtensible)
func(v *Paths, c randfill.Continue) {
c.Fill(&v.VendorExtensible)
num := c.Intn(5)
if num > 0 {
v.Paths = make(map[string]*Path)
}
for i := 0; i < num; i++ {
val := Path{}
c.Fuzz(&val)
v.Paths["/"+c.RandString()] = &val
c.Fill(&val)
v.Paths["/"+c.String(0)] = &val
}
},
func(v *SecurityScheme, c fuzz.Continue) {
func(v *SecurityScheme, c randfill.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
c.Fill(&v.Refable)
return
}
switch c.Intn(4) {
case 0:
v.Type = "apiKey"
v.Name = c.RandString() + "x"
v.Name = c.String(0) + "x"
switch c.Intn(3) {
case 0:
v.In = "query"
@ -101,17 +101,17 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
v.Type = "oauth2"
v.Flows = make(map[string]*OAuthFlow)
flow := OAuthFlow{}
flow.AuthorizationUrl = c.RandString() + "x"
flow.AuthorizationUrl = c.String(0) + "x"
v.Flows["implicit"] = &flow
flow.Scopes = make(map[string]string)
flow.Scopes["foo"] = "bar"
case 3:
v.Type = "openIdConnect"
v.OpenIdConnectUrl = "https://" + c.RandString()
v.OpenIdConnectUrl = "https://" + c.String(0)
}
v.Scheme = "basic"
},
func(v *spec.Ref, c fuzz.Continue) {
func(v *spec.Ref, c randfill.Continue) {
switch c.Intn(7) {
case 0:
*v = spec.MustCreateRef("#/components/schemas/" + randAlphanumString())
@ -127,13 +127,13 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
*v = spec.MustCreateRef("#/components/requestBodies/" + randAlphanumString())
}
},
func(v *Parameter, c fuzz.Continue) {
func(v *Parameter, c randfill.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
c.Fill(&v.Refable)
return
}
c.Fuzz(&v.ParameterProps)
c.Fuzz(&v.VendorExtensible)
c.Fill(&v.ParameterProps)
c.Fill(&v.VendorExtensible)
switch c.Intn(3) {
case 0:
@ -145,44 +145,44 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
v.In = "cookie"
}
},
func(v *RequestBody, c fuzz.Continue) {
func(v *RequestBody, c randfill.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
c.Fill(&v.Refable)
return
}
c.Fuzz(&v.RequestBodyProps)
c.Fuzz(&v.VendorExtensible)
c.Fill(&v.RequestBodyProps)
c.Fill(&v.VendorExtensible)
},
func(v *Header, c fuzz.Continue) {
func(v *Header, c randfill.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
c.Fill(&v.Refable)
return
}
c.Fuzz(&v.HeaderProps)
c.Fuzz(&v.VendorExtensible)
c.Fill(&v.HeaderProps)
c.Fill(&v.VendorExtensible)
},
func(v *ResponsesProps, c fuzz.Continue) {
c.Fuzz(&v.Default)
func(v *ResponsesProps, c randfill.Continue) {
c.Fill(&v.Default)
n := c.Intn(5)
for i := 0; i < n; i++ {
r2 := Response{}
c.Fuzz(&r2)
c.Fill(&r2)
// HTTP Status code in 100-599 Range
code := c.Intn(500) + 100
v.StatusCodeResponses = make(map[int]*Response)
v.StatusCodeResponses[code] = &r2
}
},
func(v *Response, c fuzz.Continue) {
func(v *Response, c randfill.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
c.Fill(&v.Refable)
return
}
c.Fuzz(&v.ResponseProps)
c.Fuzz(&v.VendorExtensible)
c.Fill(&v.ResponseProps)
c.Fill(&v.VendorExtensible)
},
func(v *Operation, c fuzz.Continue) {
c.FuzzNoCustom(v)
func(v *Operation, c randfill.Continue) {
c.FillNoCustom(v)
// Do not fuzz null values into the array.
for i, val := range v.SecurityRequirement {
if val == nil {
@ -196,85 +196,85 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
}
}
},
func(v *spec.Extensions, c fuzz.Continue) {
func(v *spec.Extensions, c randfill.Continue) {
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {
if *v == nil {
*v = spec.Extensions{}
}
(*v)["x-"+c.RandString()] = c.RandString()
(*v)["x-"+c.String(0)] = c.String(0)
}
},
func(v *spec.ExternalDocumentation, c fuzz.Continue) {
c.Fuzz(&v.Description)
func(v *spec.ExternalDocumentation, c randfill.Continue) {
c.Fill(&v.Description)
v.URL = "https://" + randAlphanumString()
},
func(v *spec.SchemaURL, c fuzz.Continue) {
func(v *spec.SchemaURL, c randfill.Continue) {
*v = spec.SchemaURL("https://" + randAlphanumString())
},
func(v *spec.SchemaOrBool, c fuzz.Continue) {
func(v *spec.SchemaOrBool, c randfill.Continue) {
*v = spec.SchemaOrBool{}
if c.RandBool() {
v.Allows = c.RandBool()
if c.Bool() {
v.Allows = c.Bool()
} else {
v.Schema = &spec.Schema{}
v.Allows = true
c.Fuzz(&v.Schema)
c.Fill(&v.Schema)
}
},
func(v *spec.SchemaOrArray, c fuzz.Continue) {
func(v *spec.SchemaOrArray, c randfill.Continue) {
*v = spec.SchemaOrArray{}
if c.RandBool() {
if c.Bool() {
schema := spec.Schema{}
c.Fuzz(&schema)
c.Fill(&schema)
v.Schema = &schema
} else {
v.Schemas = []spec.Schema{}
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {
schema := spec.Schema{}
c.Fuzz(&schema)
c.Fill(&schema)
v.Schemas = append(v.Schemas, schema)
}
}
},
func(v *spec.SchemaOrStringArray, c fuzz.Continue) {
if c.RandBool() {
func(v *spec.SchemaOrStringArray, c randfill.Continue) {
if c.Bool() {
*v = spec.SchemaOrStringArray{}
if c.RandBool() {
c.Fuzz(&v.Property)
if c.Bool() {
c.Fill(&v.Property)
} else {
c.Fuzz(&v.Schema)
c.Fill(&v.Schema)
}
}
},
func(v *spec.Schema, c fuzz.Continue) {
func(v *spec.Schema, c randfill.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Ref)
c.Fill(&v.Ref)
return
}
if c.RandBool() {
if c.Bool() {
// file schema
c.Fuzz(&v.Default)
c.Fuzz(&v.Description)
c.Fuzz(&v.Example)
c.Fuzz(&v.ExternalDocs)
c.Fill(&v.Default)
c.Fill(&v.Description)
c.Fill(&v.Example)
c.Fill(&v.ExternalDocs)
c.Fuzz(&v.Format)
c.Fuzz(&v.ReadOnly)
c.Fuzz(&v.Required)
c.Fuzz(&v.Title)
c.Fill(&v.Format)
c.Fill(&v.ReadOnly)
c.Fill(&v.Required)
c.Fill(&v.Title)
v.Type = spec.StringOrArray{"file"}
} else {
// normal schema
c.Fuzz(&v.SchemaProps)
c.Fuzz(&v.SwaggerSchemaProps)
c.Fuzz(&v.VendorExtensible)
c.Fuzz(&v.ExtraProps)
c.Fill(&v.SchemaProps)
c.Fill(&v.SwaggerSchemaProps)
c.Fill(&v.VendorExtensible)
c.Fill(&v.ExtraProps)
}
},

25
vendor/modules.txt vendored

@ -951,11 +951,9 @@ github.com/golang-jwt/jwt/v5
# github.com/golang/protobuf v1.5.4
## explicit; go 1.17
github.com/golang/protobuf/proto
github.com/golang/protobuf/ptypes
github.com/golang/protobuf/ptypes/any
github.com/golang/protobuf/ptypes/duration
github.com/golang/protobuf/ptypes/empty
github.com/golang/protobuf/ptypes/timestamp
# github.com/golang/snappy v1.0.0
## explicit
github.com/golang/snappy
@ -965,8 +963,8 @@ github.com/google/btree
# github.com/google/flatbuffers v25.2.10+incompatible
## explicit
github.com/google/flatbuffers/go
# github.com/google/gnostic-models v0.6.8
## explicit; go 1.18
# github.com/google/gnostic-models v0.6.9
## explicit; go 1.21
github.com/google/gnostic-models/compiler
github.com/google/gnostic-models/extensions
github.com/google/gnostic-models/jsonschema
@ -982,10 +980,6 @@ github.com/google/go-cmp/cmp/internal/value
# github.com/google/go-querystring v1.1.0
## explicit; go 1.10
github.com/google/go-querystring/query
# github.com/google/gofuzz v1.2.0
## explicit; go 1.12
github.com/google/gofuzz
github.com/google/gofuzz/bytesource
# github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad
## explicit; go 1.22
github.com/google/pprof/profile
@ -2518,12 +2512,13 @@ k8s.io/api/storage/v1
k8s.io/api/storage/v1alpha1
k8s.io/api/storage/v1beta1
k8s.io/api/storagemigration/v1alpha1
# k8s.io/apimachinery v0.32.4
## explicit; go 1.23.0
# k8s.io/apimachinery v0.33.0
## explicit; go 1.24.0
k8s.io/apimachinery/pkg/api/equality
k8s.io/apimachinery/pkg/api/errors
k8s.io/apimachinery/pkg/api/meta
k8s.io/apimachinery/pkg/api/meta/testrestmapper
k8s.io/apimachinery/pkg/api/operation
k8s.io/apimachinery/pkg/api/resource
k8s.io/apimachinery/pkg/api/validation
k8s.io/apimachinery/pkg/apis/meta/internalversion
@ -2724,8 +2719,8 @@ k8s.io/klog/v2/internal/dbg
k8s.io/klog/v2/internal/serialize
k8s.io/klog/v2/internal/severity
k8s.io/klog/v2/internal/sloghandler
# k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f
## explicit; go 1.20
# k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
## explicit; go 1.21
k8s.io/kube-openapi/pkg/cached
k8s.io/kube-openapi/pkg/common
k8s.io/kube-openapi/pkg/handler3
@ -2791,7 +2786,11 @@ rsc.io/binaryregexp/syntax
## explicit; go 1.21
sigs.k8s.io/json
sigs.k8s.io/json/internal/golang/encoding/json
# sigs.k8s.io/structured-merge-diff/v4 v4.4.2
# sigs.k8s.io/randfill v1.0.0
## explicit; go 1.18
sigs.k8s.io/randfill
sigs.k8s.io/randfill/bytesource
# sigs.k8s.io/structured-merge-diff/v4 v4.6.0
## explicit; go 1.13
sigs.k8s.io/structured-merge-diff/v4/fieldpath
sigs.k8s.io/structured-merge-diff/v4/merge

@ -0,0 +1,43 @@
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
## Getting Started
We have full documentation on how to get started contributing here:
<!---
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
-->
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
## Mentorship
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
<!---
Custom Information - if you're copying this template for the first time you can add custom content here, for example:
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/kubernetes-users) - Replace `kubernetes-users` with your slack channel string, this will send users directly to your channel.
- [Mailing list](URL)
-->
## Project Management
The [maintainers](https://github.com/kubernetes-sigs/randfill/blob/main/OWNERS_ALIASES#L12) of this project (and often others who have official positions on the [contributor ladder](https://github.com/kubernetes-sigs/randfill/blob/main/OWNERS_ALIASES)) are responsible for performing project management which oversees development and maintenance of the API, tests, tools, e.t.c. While we try to be generally flexible when it comes to the management of individual pieces (such as Issues or PRs), we have some rules and guidelines which help us plan, coordinate and reduce waste. In this section you'll find some rules/guidelines for contributors related to project management which may extend or go beyond what you would find in the standard [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide).
### Bumping stale and closed Issues & PRs
Maintainers are ultimately responsible for triaging new issues and PRs, accepting or declining them, deciding priority and fitting them into milestones intended for future releases. Bots are responsible for marking issues and PRs which stagnate as stale, or closing them if progress does not continue for a long period of time. Due to the nature of this community-driven development effort (we do not have dedicated engineering resources, we rely on the community which is effectively "volunteer time") **not all issues can be accepted, prioritized or completed**.
You may find times when an issue you're subscribed to and interested in seems to stagnate, or perhaps gets auto-closed. Prior to bumping or directly re-opening issues yourself, we generally ask that you bring these up for discussion on the agenda for one of our community syncs if possible, or bring them up for discussion in Slack or the mailing list as this gives us a better opportunity to discuss the issue and determine viability and logistics. If feasible we **highly recommend being ready to contribute directly** to any stale or unprioritized effort that you want to see move forward, as **the best way to ensure progress is to engage with the community and personally invest time**.
We (the community) aren't opposed to making exceptions in some cases, but when in doubt please follow the above guidelines before bumping closed or stale issues if you're not ready to personally invest time in them. We are responsible for managing these and without further context or engagement we may set these back to how they were previously organized.

@ -1,4 +1,3 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@ -179,7 +178,7 @@
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 "[]"
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
@ -187,7 +186,8 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2014 The gofuzz Authors
Copyright 2025 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.

@ -0,0 +1,24 @@
When donating the randfill project to the CNCF, we could not reach all the
gofuzz contributors to sign the CNCF CLA. As such, according to the CNCF rules
to donate a repository, we must add a NOTICE referencing section 7 of the CLA
with a list of developers who could not be reached.
`7. Should You wish to submit work that is not Your original creation, You may
submit it to the Foundation separately from any Contribution, identifying the
complete details of its source and of any license or other restriction
(including, but not limited to, related patents, trademarks, and license
agreements) of which you are personally aware, and conspicuously marking the
work as "Submitted on behalf of a third-party: [named here]".`
Submitted on behalf of a third-party: @dnephin (Daniel Nephin)
Submitted on behalf of a third-party: @AlekSi (Alexey Palazhchenko)
Submitted on behalf of a third-party: @bbigras (Bruno Bigras)
Submitted on behalf of a third-party: @samirkut (Samir)
Submitted on behalf of a third-party: @posener (Eyal Posener)
Submitted on behalf of a third-party: @Ashikpaul (Ashik Paul)
Submitted on behalf of a third-party: @kwongtailau (Kwongtai)
Submitted on behalf of a third-party: @ericcornelissen (Eric Cornelissen)
Submitted on behalf of a third-party: @eclipseo (Robert-André Mauchin)
Submitted on behalf of a third-party: @yanzhoupan (Andrew Pan)
Submitted on behalf of a third-party: @STRRL (Zhiqiang ZHOU)
Submitted on behalf of a third-party: @disconnect3d (Disconnect3d)

@ -0,0 +1,8 @@
# See the OWNERS docs at https://go.k8s.io/owners
# See the OWNERS_ALIASES file at https://github.com/kubernetes-sigs/randfill/blob/main/OWNERS_ALIASES for a list of members for each alias.
approvers:
- sig-testing-leads
- thockin
reviewers: []

@ -0,0 +1,14 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
# This file should be kept in sync with k/org.
aliases:
# Reference: https://github.com/kubernetes/org/blob/main/OWNERS_ALIASES
sig-testing-leads:
- BenTheElder
- alvaroaleman
- aojea
- cjwagner
- jbpratt
- michelle192837
- pohly
- xmcqueen

@ -1,39 +1,46 @@
gofuzz
randfill
======
gofuzz is a library for populating go objects with random values.
randfill is a library for populating go objects with random values.
[![GoDoc](https://godoc.org/github.com/google/gofuzz?status.svg)](https://godoc.org/github.com/google/gofuzz)
[![Travis](https://travis-ci.org/google/gofuzz.svg?branch=master)](https://travis-ci.org/google/gofuzz)
This is a fork of github.com/google/gofuzz, which was archived.
NOTE: This repo is supported only for use within Kubernetes. It is not our
intention to support general use. That said, if it works for you, that's
great! If you have a problem, please feel free to file an issue, but be aware
that it may not be a priority for us to fix it unless it is affecting
Kubernetes. PRs are welcome, within reason.
[![GoDoc](https://godoc.org/sigs.k8s.io/randfill?status.svg)](https://godoc.org/sigs.k8s.io/randfill)
This is useful for testing:
* Do your project's objects really serialize/unserialize correctly in all cases?
* Is there an incorrectly formatted object that will cause your project to panic?
Import with ```import "github.com/google/gofuzz"```
Import with ```import "sigs.k8s.io/randfill"```
You can use it on single variables:
```go
f := fuzz.New()
f := randfill.New()
var myInt int
f.Fuzz(&myInt) // myInt gets a random value.
f.Fill(&myInt) // myInt gets a random value.
```
You can use it on maps:
```go
f := fuzz.New().NilChance(0).NumElements(1, 1)
f := randfill.New().NilChance(0).NumElements(1, 1)
var myMap map[ComplexKeyType]string
f.Fuzz(&myMap) // myMap will have exactly one element.
f.Fill(&myMap) // myMap will have exactly one element.
```
Customize the chance of getting a nil pointer:
```go
f := fuzz.New().NilChance(.5)
f := randfill.New().NilChance(.5)
var fancyStruct struct {
A, B, C, D *string
}
f.Fuzz(&fancyStruct) // About half the pointers should be set.
f.Fill(&fancyStruct) // About half the pointers should be set.
```
You can even customize the randomization completely if needed:
@ -49,25 +56,27 @@ type MyInfo struct {
BInfo *string
}
f := fuzz.New().NilChance(0).Funcs(
func(e *MyInfo, c fuzz.Continue) {
f := randfill.New().NilChance(0).Funcs(
func(e *MyInfo, c randfill.Continue) {
switch c.Intn(2) {
case 0:
e.Type = A
c.Fuzz(&e.AInfo)
c.Fill(&e.AInfo)
case 1:
e.Type = B
c.Fuzz(&e.BInfo)
c.Fill(&e.BInfo)
}
},
)
var myObject MyInfo
f.Fuzz(&myObject) // Type will correspond to whether A or B info is set.
f.Fill(&myObject) // Type will correspond to whether A or B info is set.
```
See more examples in ```example_test.go```.
## dvyukov/go-fuzz integration
You can use this library for easier [go-fuzz](https://github.com/dvyukov/go-fuzz)ing.
go-fuzz provides the user a byte-slice, which should be converted to different inputs
for the tested function. This library can help convert the byte slice. Consider for
@ -76,11 +85,11 @@ example a fuzz test for a the function `mypackage.MyFunc` that takes an int argu
// +build gofuzz
package mypackage
import fuzz "github.com/google/gofuzz"
import "sigs.k8s.io/randfill"
func Fuzz(data []byte) int {
var i int
fuzz.NewFromGoFuzz(data).Fuzz(&i)
randfill.NewFromGoFuzz(data).Fill(&i)
MyFunc(i)
return 0
}

@ -0,0 +1,16 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Committee to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
thockin
BenTheElder
aojea
pohly

@ -0,0 +1,3 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

@ -0,0 +1,682 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Copyright 2014 The gofuzz Authors.
Copyright 2025 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 randfill is a library for populating go objects with random values.
package randfill
import (
"fmt"
"math/rand"
"reflect"
"regexp"
"sync"
"time"
"unsafe"
"strings"
"sigs.k8s.io/randfill/bytesource"
)
// funcMap is a map from a type to a function that randfills that type. The
// function is a reflect.Value because the type being filled is different for
// each func.
type funcMap map[reflect.Type]reflect.Value
// Filler knows how to fill any object with random fields.
type Filler struct {
customFuncs funcMap
defaultFuncs funcMap
r *rand.Rand
nilChance float64
minElements int
maxElements int
maxDepth int
allowUnexportedFields bool
skipFieldPatterns []*regexp.Regexp
lock sync.Mutex
}
// New returns a new Filler. Customize your Filler further by calling Funcs,
// RandSource, NilChance, or NumElements in any order.
func New() *Filler {
return NewWithSeed(time.Now().UnixNano())
}
func NewWithSeed(seed int64) *Filler {
f := &Filler{
defaultFuncs: funcMap{
reflect.TypeOf(&time.Time{}): reflect.ValueOf(randfillTime),
},
customFuncs: funcMap{},
r: rand.New(rand.NewSource(seed)),
nilChance: .2,
minElements: 1,
maxElements: 10,
maxDepth: 100,
allowUnexportedFields: false,
}
return f
}
// NewFromGoFuzz is a helper function that enables using randfill (this
// project) with go-fuzz (https://github.com/dvyukov/go-fuzz) for continuous
// fuzzing. Essentially, it enables translating the fuzzing bytes from
// go-fuzz to any Go object using this library.
//
// This implementation promises a constant translation from a given slice of
// bytes to the fuzzed objects. This promise will remain over future
// versions of Go and of this library.
//
// Note: the returned Filler should not be shared between multiple goroutines,
// as its deterministic output will no longer be available.
//
// Example: use go-fuzz to test the function `MyFunc(int)` in the package
// `mypackage`. Add the file: "mypackage_fuzz.go" with the content:
//
// // +build gofuzz
// package mypackage
// import "sigs.k8s.io/randfill"
//
// func Fuzz(data []byte) int {
// var i int
// randfill.NewFromGoFuzz(data).Fill(&i)
// MyFunc(i)
// return 0
// }
func NewFromGoFuzz(data []byte) *Filler {
return New().RandSource(bytesource.New(data))
}
// Funcs registers custom fill functions for this Filler.
//
// Each entry in customFuncs must be a function taking two parameters.
// The first parameter must be a pointer or map. It is the variable that
// function will fill with random data. The second parameter must be a
// randfill.Continue, which will provide a source of randomness and a way
// to automatically continue filling smaller pieces of the first parameter.
//
// These functions are called sensibly, e.g., if you wanted custom string
// filling, the function `func(s *string, c randfill.Continue)` would get
// called and passed the address of strings. Maps and pointers will always
// be made/new'd for you, ignoring the NilChance option. For slices, it
// doesn't make much sense to pre-create them--Filler doesn't know how
// long you want your slice--so take a pointer to a slice, and make it
// yourself. (If you don't want your map/pointer type pre-made, take a
// pointer to it, and make it yourself.) See the examples for a range of
// custom functions.
//
// If a function is already registered for a type, and a new function is
// provided, the previous function will be replaced with the new one.
func (f *Filler) Funcs(customFuncs ...interface{}) *Filler {
for i := range customFuncs {
v := reflect.ValueOf(customFuncs[i])
if v.Kind() != reflect.Func {
panic("Filler.Funcs: all arguments must be functions")
}
t := v.Type()
if t.NumIn() != 2 || t.NumOut() != 0 {
panic("Filler.Funcs: all customFuncs must have 2 arguments and 0 returns")
}
argT := t.In(0)
switch argT.Kind() {
case reflect.Ptr, reflect.Map:
default:
panic("Filler.Funcs: customFuncs' first argument must be a pointer or map type")
}
if t.In(1) != reflect.TypeOf(Continue{}) {
panic("Filler.Funcs: customFuncs' second argument must be a randfill.Continue")
}
f.customFuncs[argT] = v
}
return f
}
// RandSource causes this Filler to get values from the given source of
// randomness. Use this if you want deterministic filling.
func (f *Filler) RandSource(s rand.Source) *Filler {
f.r = rand.New(s)
return f
}
// NilChance sets the probability of creating a nil pointer, map, or slice to
// 'p'. 'p' should be between 0 (no nils) and 1 (all nils), inclusive.
func (f *Filler) NilChance(p float64) *Filler {
if p < 0 || p > 1 {
panic("Filler.NilChance: p must be between 0 and 1, inclusive")
}
f.nilChance = p
return f
}
// NumElements sets the minimum and maximum number of elements that will be
// added to a non-nil map or slice.
func (f *Filler) NumElements(min, max int) *Filler {
if min < 0 {
panic("Filler.NumElements: min must be >= 0")
}
if min > max {
panic("Filler.NumElements: min must be <= max")
}
f.minElements = min
f.maxElements = max
return f
}
func (f *Filler) genElementCount() int {
if f.minElements == f.maxElements {
return f.minElements
}
return f.minElements + f.r.Intn(f.maxElements-f.minElements+1)
}
func (f *Filler) genShouldFill() bool {
return f.r.Float64() >= f.nilChance
}
// MaxDepth sets the maximum number of recursive fill calls that will be made
// before stopping. This includes struct members, pointers, and map and slice
// elements.
func (f *Filler) MaxDepth(d int) *Filler {
f.maxDepth = d
return f
}
// AllowUnexportedFields defines whether to fill unexported fields.
func (f *Filler) AllowUnexportedFields(flag bool) *Filler {
f.allowUnexportedFields = flag
return f
}
// SkipFieldsWithPattern tells this Filler to skip any field whose name matches
// the supplied pattern. Call this multiple times if needed. This is useful to
// skip XXX_ fields generated by protobuf.
func (f *Filler) SkipFieldsWithPattern(pattern *regexp.Regexp) *Filler {
f.skipFieldPatterns = append(f.skipFieldPatterns, pattern)
return f
}
// SimpleSelfFiller represents an object that knows how to randfill itself.
//
// Unlike NativeSelfFiller, this interface does not cause the type in question
// to depend on the randfill package. This is most useful for simple types. For
// more complex types, consider using NativeSelfFiller.
type SimpleSelfFiller interface {
// RandFill fills the current object with random data.
RandFill(r *rand.Rand)
}
// NativeSelfFiller represents an object that knows how to randfill itself.
//
// Unlike SimpleSelfFiller, this interface allows for recursive filling of
// child objects with the same rules as the parent Filler.
type NativeSelfFiller interface {
// RandFill fills the current object with random data.
RandFill(c Continue)
}
// Fill recursively fills all of obj's fields with something random. First
// this tries to find a custom fill function (see Funcs). If there is no
// custom function, this tests whether the object implements SimpleSelfFiller
// or NativeSelfFiller and if so, calls RandFill on it to fill itself. If that
// fails, this will see if there is a default fill function provided by this
// package. If all of that fails, this will generate random values for all
// primitive fields and then recurse for all non-primitives.
//
// 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. Exported (public) fields can always be set, and if
// the AllowUnexportedFields() modifier was called it can try to set unexported
// (private) fields, too.
//
// This is intended for tests, so will panic on bad input or unimplemented
// types. This method takes a lock for the whole Filler, so it is not
// reentrant. See Continue.
func (f *Filler) Fill(obj interface{}) {
f.lock.Lock()
defer f.lock.Unlock()
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("Filler.Fill: obj must be a pointer")
}
v = v.Elem()
f.fillWithContext(v, 0)
}
// FillNoCustom is just like Fill, except that any custom fill function for
// obj's type will not be called and obj will not be tested for
// SimpleSelfFiller or NativeSelfFiller. This applies only to obj and not other
// instances of obj's type or to obj's child fields.
//
// obj must be a pointer. Exported (public) fields can always be set, and if
// the AllowUnexportedFields() modifier was called it can try to set unexported
// (private) fields, too.
//
// This is intended for tests, so will panic on bad input or unimplemented
// types. This method takes a lock for the whole Filler, so it is not
// reentrant. See Continue.
func (f *Filler) FillNoCustom(obj interface{}) {
f.lock.Lock()
defer f.lock.Unlock()
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("Filler.FillNoCustom: obj must be a pointer")
}
v = v.Elem()
f.fillWithContext(v, flagNoCustomFill)
}
const (
// Do not try to find a custom fill function. Does not apply recursively.
flagNoCustomFill uint64 = 1 << iota
)
func (f *Filler) fillWithContext(v reflect.Value, flags uint64) {
fc := &fillerContext{filler: f}
fc.doFill(v, flags)
}
// fillerContext carries context about a single filling run, which lets Filler
// be thread-safe.
type fillerContext struct {
filler *Filler
curDepth int
}
func (fc *fillerContext) doFill(v reflect.Value, flags uint64) {
if fc.curDepth >= fc.filler.maxDepth {
return
}
fc.curDepth++
defer func() { fc.curDepth-- }()
if !v.CanSet() {
if !fc.filler.allowUnexportedFields || !v.CanAddr() {
return
}
v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()
}
if flags&flagNoCustomFill == 0 {
// Check for both pointer and non-pointer custom functions.
if v.CanAddr() && fc.tryCustom(v.Addr()) {
return
}
if fc.tryCustom(v) {
return
}
}
if fn, ok := fillFuncMap[v.Kind()]; ok {
fn(v, fc.filler.r)
return
}
switch v.Kind() {
case reflect.Map:
if fc.filler.genShouldFill() {
v.Set(reflect.MakeMap(v.Type()))
n := fc.filler.genElementCount()
for i := 0; i < n; i++ {
key := reflect.New(v.Type().Key()).Elem()
fc.doFill(key, 0)
val := reflect.New(v.Type().Elem()).Elem()
fc.doFill(val, 0)
v.SetMapIndex(key, val)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Ptr:
if fc.filler.genShouldFill() {
v.Set(reflect.New(v.Type().Elem()))
fc.doFill(v.Elem(), 0)
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Slice:
if fc.filler.genShouldFill() {
n := fc.filler.genElementCount()
v.Set(reflect.MakeSlice(v.Type(), n, n))
for i := 0; i < n; i++ {
fc.doFill(v.Index(i), 0)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Array:
if fc.filler.genShouldFill() {
n := v.Len()
for i := 0; i < n; i++ {
fc.doFill(v.Index(i), 0)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
skipField := false
fieldName := v.Type().Field(i).Name
for _, pattern := range fc.filler.skipFieldPatterns {
if pattern.MatchString(fieldName) {
skipField = true
break
}
}
if !skipField {
fc.doFill(v.Field(i), 0)
}
}
case reflect.Chan:
fallthrough
case reflect.Func:
fallthrough
case reflect.Interface:
fallthrough
default:
panic(fmt.Sprintf("can't fill type %v, kind %v", v.Type(), v.Kind()))
}
}
// tryCustom searches for custom handlers, and returns true iff it finds a match
// and successfully randomizes v.
func (fc *fillerContext) tryCustom(v reflect.Value) bool {
// First: see if we have a fill function for it.
doCustom, ok := fc.filler.customFuncs[v.Type()]
if !ok {
// Second: see if it can fill itself.
if v.CanInterface() {
intf := v.Interface()
if fillable, ok := intf.(SimpleSelfFiller); ok {
fillable.RandFill(fc.filler.r)
return true
}
if fillable, ok := intf.(NativeSelfFiller); ok {
fillable.RandFill(Continue{fc: fc, Rand: fc.filler.r})
return true
}
}
// Finally: see if there is a default fill function.
doCustom, ok = fc.filler.defaultFuncs[v.Type()]
if !ok {
return false
}
}
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.New(v.Type().Elem()))
}
case reflect.Map:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.MakeMap(v.Type()))
}
default:
return false
}
doCustom.Call([]reflect.Value{
v,
reflect.ValueOf(Continue{
fc: fc,
Rand: fc.filler.r,
}),
})
return true
}
// Continue can be passed to custom fill functions to allow them to use
// the correct source of randomness and to continue filling their members.
type Continue struct {
fc *fillerContext
// For convenience, Continue implements rand.Rand via embedding.
// Use this for generating any randomness if you want your filling
// to be repeatable for a given seed.
*rand.Rand
}
// Fill continues filling obj. obj must be a pointer or a reflect.Value of a
// pointer. See Filler.Fill.
func (c Continue) Fill(obj interface{}) {
v, ok := obj.(reflect.Value)
if !ok {
v = reflect.ValueOf(obj)
}
if v.Kind() != reflect.Ptr {
panic("Continue.Fill: obj must be a pointer")
}
v = v.Elem()
c.fc.doFill(v, 0)
}
// FillNoCustom continues filling obj, except that any custom fill function for
// obj's type will not be called and obj will not be tested for
// SimpleSelfFiller or NativeSelfFiller. See Filler.FillNoCustom.
func (c Continue) FillNoCustom(obj interface{}) {
v, ok := obj.(reflect.Value)
if !ok {
v = reflect.ValueOf(obj)
}
if v.Kind() != reflect.Ptr {
panic("Continue.FillNoCustom: obj must be a pointer")
}
v = v.Elem()
c.fc.doFill(v, flagNoCustomFill)
}
const defaultStringMaxLen = 20
// String makes a random string up to n characters long. If n is 0, the default
// size range is [0-20). The returned string may include a variety of (valid)
// UTF-8 encodings.
func (c Continue) String(n int) string {
return randString(c.Rand, n)
}
// Uint64 makes random 64 bit numbers.
// Weirdly, rand doesn't have a function that gives you 64 random bits.
func (c Continue) Uint64() uint64 {
return randUint64(c.Rand)
}
// Bool returns true or false randomly.
func (c Continue) Bool() bool {
return randBool(c.Rand)
}
func fillInt(v reflect.Value, r *rand.Rand) {
v.SetInt(int64(randUint64(r)))
}
func fillUint(v reflect.Value, r *rand.Rand) {
v.SetUint(randUint64(r))
}
func randfillTime(t *time.Time, c Continue) {
var sec, nsec int64
// Allow for about 1000 years of random time values, which keeps things
// like JSON parsing reasonably happy.
sec = c.Rand.Int63n(1000 * 365 * 24 * 60 * 60)
// Nanosecond values greater than 1Bn are technically allowed but result in
// time.Time values with invalid timezone offsets.
nsec = c.Rand.Int63n(999999999)
*t = time.Unix(sec, nsec)
}
var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){
reflect.Bool: func(v reflect.Value, r *rand.Rand) {
v.SetBool(randBool(r))
},
reflect.Int: fillInt,
reflect.Int8: fillInt,
reflect.Int16: fillInt,
reflect.Int32: fillInt,
reflect.Int64: fillInt,
reflect.Uint: fillUint,
reflect.Uint8: fillUint,
reflect.Uint16: fillUint,
reflect.Uint32: fillUint,
reflect.Uint64: fillUint,
reflect.Uintptr: fillUint,
reflect.Float32: func(v reflect.Value, r *rand.Rand) {
v.SetFloat(float64(r.Float32()))
},
reflect.Float64: func(v reflect.Value, r *rand.Rand) {
v.SetFloat(r.Float64())
},
reflect.Complex64: func(v reflect.Value, r *rand.Rand) {
v.SetComplex(complex128(complex(r.Float32(), r.Float32())))
},
reflect.Complex128: func(v reflect.Value, r *rand.Rand) {
v.SetComplex(complex(r.Float64(), r.Float64()))
},
reflect.String: func(v reflect.Value, r *rand.Rand) {
v.SetString(randString(r, 0))
},
reflect.UnsafePointer: func(v reflect.Value, r *rand.Rand) {
panic("filling of UnsafePointers is not implemented")
},
}
// randBool returns true or false randomly.
func randBool(r *rand.Rand) bool {
return r.Int31()&(1<<30) == 0
}
type int63nPicker interface {
Int63n(int64) int64
}
// UnicodeRange describes a sequential range of unicode characters.
// Last must be numerically greater than First.
type UnicodeRange struct {
First, Last rune
}
// UnicodeRanges describes an arbitrary number of sequential ranges of unicode characters.
// To be useful, each range must have at least one character (First <= Last) and
// there must be at least one range.
type UnicodeRanges []UnicodeRange
// choose returns a random unicode character from the given range, using the
// given randomness source.
func (ur UnicodeRange) choose(r int63nPicker) rune {
count := int64(ur.Last - ur.First + 1)
return ur.First + rune(r.Int63n(count))
}
// CustomStringFillFunc constructs a FillFunc which produces random strings.
// Each character is selected from the range ur. If there are no characters
// in the range (cr.Last < cr.First), this will panic.
func (ur UnicodeRange) CustomStringFillFunc(n int) func(s *string, c Continue) {
ur.check()
return func(s *string, c Continue) {
*s = ur.randString(c.Rand, n)
}
}
// check is a function that used to check whether the first of ur(UnicodeRange)
// is greater than the last one.
func (ur UnicodeRange) check() {
if ur.Last < ur.First {
panic("UnicodeRange.check: the last encoding must be greater than the first")
}
}
// randString of UnicodeRange makes a random string up to 20 characters long.
// Each character is selected form ur(UnicodeRange).
func (ur UnicodeRange) randString(r *rand.Rand, max int) string {
if max == 0 {
max = defaultStringMaxLen
}
n := r.Intn(max)
sb := strings.Builder{}
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteRune(ur.choose(r))
}
return sb.String()
}
// defaultUnicodeRanges sets a default unicode range when users do not set
// CustomStringFillFunc() but want to fill strings.
var defaultUnicodeRanges = UnicodeRanges{
{' ', '~'}, // ASCII characters
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
}
// CustomStringFillFunc constructs a FillFunc which produces random strings.
// Each character is selected from one of the ranges of ur(UnicodeRanges).
// Each range has an equal probability of being chosen. If there are no ranges,
// or a selected range has no characters (.Last < .First), this will panic.
// Do not modify any of the ranges in ur after calling this function.
func (ur UnicodeRanges) CustomStringFillFunc(n int) func(s *string, c Continue) {
// Check unicode ranges slice is empty.
if len(ur) == 0 {
panic("UnicodeRanges is empty")
}
// if not empty, each range should be checked.
for i := range ur {
ur[i].check()
}
return func(s *string, c Continue) {
*s = ur.randString(c.Rand, n)
}
}
// randString of UnicodeRanges makes a random string up to 20 characters long.
// Each character is selected form one of the ranges of ur(UnicodeRanges),
// and each range has an equal probability of being chosen.
func (ur UnicodeRanges) randString(r *rand.Rand, max int) string {
if max == 0 {
max = defaultStringMaxLen
}
n := r.Intn(max)
sb := strings.Builder{}
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteRune(ur[r.Intn(len(ur))].choose(r))
}
return sb.String()
}
// randString makes a random string up to 20 characters long. The returned string
// may include a variety of (valid) UTF-8 encodings.
func randString(r *rand.Rand, max int) string {
return defaultUnicodeRanges.randString(r, max)
}
// randUint64 makes random 64 bit numbers.
// Weirdly, rand doesn't have a function that gives you 64 random bits.
func randUint64(r *rand.Rand) uint64 {
return uint64(r.Uint32())<<32 | uint64(r.Uint32())
}

@ -33,6 +33,9 @@ type UpdaterBuilder struct {
Converter Converter
IgnoreFilter map[fieldpath.APIVersion]fieldpath.Filter
// IgnoredFields provides a set of fields to ignore for each
IgnoredFields map[fieldpath.APIVersion]*fieldpath.Set
// Stop comparing the new object with old object after applying.
// This was initially used to avoid spurious etcd update, but
// since that's vastly inefficient, we've come-up with a better
@ -46,6 +49,7 @@ func (u *UpdaterBuilder) BuildUpdater() *Updater {
return &Updater{
Converter: u.Converter,
IgnoreFilter: u.IgnoreFilter,
IgnoredFields: u.IgnoredFields,
returnInputOnNoop: u.ReturnInputOnNoop,
}
}
@ -56,6 +60,9 @@ type Updater struct {
// Deprecated: This will eventually become private.
Converter Converter
// Deprecated: This will eventually become private.
IgnoredFields map[fieldpath.APIVersion]*fieldpath.Set
// Deprecated: This will eventually become private.
IgnoreFilter map[fieldpath.APIVersion]fieldpath.Filter
@ -70,8 +77,19 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions := map[fieldpath.APIVersion]*typed.Comparison{
version: compare.FilterFields(s.IgnoreFilter[version]),
var versions map[fieldpath.APIVersion]*typed.Comparison
if s.IgnoredFields != nil && s.IgnoreFilter != nil {
return nil, nil, fmt.Errorf("IgnoreFilter and IgnoreFilter may not both be set")
}
if s.IgnoredFields != nil {
versions = map[fieldpath.APIVersion]*typed.Comparison{
version: compare.ExcludeFields(s.IgnoredFields[version]),
}
} else {
versions = map[fieldpath.APIVersion]*typed.Comparison{
version: compare.FilterFields(s.IgnoreFilter[version]),
}
}
for manager, managerSet := range managers {
@ -101,7 +119,12 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
if err != nil {
return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions[managerSet.APIVersion()] = compare.FilterFields(s.IgnoreFilter[managerSet.APIVersion()])
if s.IgnoredFields != nil {
versions[managerSet.APIVersion()] = compare.ExcludeFields(s.IgnoredFields[managerSet.APIVersion()])
} else {
versions[managerSet.APIVersion()] = compare.FilterFields(s.IgnoreFilter[managerSet.APIVersion()])
}
}
conflictSet := managerSet.Set().Intersection(compare.Modified.Union(compare.Added))
@ -154,7 +177,16 @@ func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldp
managers[manager] = fieldpath.NewVersionedSet(fieldpath.NewSet(), version, false)
}
set := managers[manager].Set().Difference(compare.Removed).Union(compare.Modified).Union(compare.Added)
ignoreFilter := s.IgnoreFilter[version]
if s.IgnoredFields != nil && s.IgnoreFilter != nil {
return nil, nil, fmt.Errorf("IgnoreFilter and IgnoreFilter may not both be set")
}
var ignoreFilter fieldpath.Filter
if s.IgnoredFields != nil {
ignoreFilter = fieldpath.NewExcludeSetFilter(s.IgnoredFields[version])
} else {
ignoreFilter = s.IgnoreFilter[version]
}
if ignoreFilter != nil {
set = ignoreFilter.Filter(set)
}
@ -189,7 +221,15 @@ func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fiel
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
}
ignoreFilter := s.IgnoreFilter[version]
if s.IgnoredFields != nil && s.IgnoreFilter != nil {
return nil, nil, fmt.Errorf("IgnoreFilter and IgnoreFilter may not both be set")
}
var ignoreFilter fieldpath.Filter
if s.IgnoredFields != nil {
ignoreFilter = fieldpath.NewExcludeSetFilter(s.IgnoredFields[version])
} else {
ignoreFilter = s.IgnoreFilter[version]
}
if ignoreFilter != nil {
set = ignoreFilter.Filter(set)
}

@ -32,6 +32,21 @@ const (
AllowDuplicates ValidationOptions = iota
)
// extractItemsOptions is the options available when extracting items.
type extractItemsOptions struct {
appendKeyFields bool
}
type ExtractItemsOption func(*extractItemsOptions)
// WithAppendKeyFields configures ExtractItems to include key fields.
// It is exported for use in configuring ExtractItems.
func WithAppendKeyFields() ExtractItemsOption {
return func(opts *extractItemsOptions) {
opts.appendKeyFields = true
}
}
// 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.
@ -187,7 +202,37 @@ func (tv TypedValue) RemoveItems(items *fieldpath.Set) *TypedValue {
}
// ExtractItems returns a value with only the provided list or map items extracted from the value.
func (tv TypedValue) ExtractItems(items *fieldpath.Set) *TypedValue {
func (tv TypedValue) ExtractItems(items *fieldpath.Set, opts ...ExtractItemsOption) *TypedValue {
options := &extractItemsOptions{}
for _, opt := range opts {
opt(options)
}
if options.appendKeyFields {
tvPathSet, err := tv.ToFieldSet()
if err == nil {
keyFieldPathSet := fieldpath.NewSet()
items.Iterate(func(path fieldpath.Path) {
if !tvPathSet.Has(path) {
return
}
for i, pe := range path {
if pe.Key == nil {
continue
}
for _, keyField := range *pe.Key {
keyName := keyField.Name
// Create a new slice with the same elements as path[:i+1], but set its capacity to len(path[:i+1]).
// This ensures that appending to keyFieldPath creates a new underlying array, avoiding accidental
// modification of the original slice (path).
keyFieldPath := append(path[:i+1:i+1], fieldpath.PathElement{FieldName: &keyName})
keyFieldPathSet.Insert(keyFieldPath)
}
}
})
items = items.Union(keyFieldPathSet)
}
}
tv.value = removeItemsWithSchema(tv.value, items, tv.schema, tv.typeRef, true)
return &tv
}

@ -43,7 +43,7 @@ func IntCompare(lhs, rhs int64) int {
func BoolCompare(lhs, rhs bool) int {
if lhs == rhs {
return 0
} else if lhs == false {
} else if !lhs {
return -1
}
return 1

Loading…
Cancel
Save