Compare commits

...

499 Commits

Author SHA1 Message Date
Kubernetes Publisher 04507a37f6 Merge pull request #132942 from thockin/kyaml
Add KYAML support to kubectl

Kubernetes-commit: 1451dd1b0873e801e082f3a06a52685bcd68dcac
2025-07-25 02:42:58 +00:00
Kubernetes Publisher 50e39b11cd Merge pull request #132935 from benluddy/cbor-bump-custom-marshalers
KEP-4222: Adopt text and JSON transcoding support for CBOR.

Kubernetes-commit: dfc0998baa4d6c2cd630aa3c5b8def4e9b1fcd8e
2025-07-24 22:42:58 +00:00
Tim Hockin 7d108e8f2c Re-vendor sigs.k8s.io/yaml @ v1.6.0
Kubernetes-commit: 8182a27f3b0769cefe1bcebfb938a7bafd51c88e
2025-07-24 11:46:03 -07:00
Kubernetes Publisher 58c4eb072e Merge pull request #133130 from ylink-lfs/chore/residual_boolptr_removal
chore: residual boolptr and intptr removal with ptr.To

Kubernetes-commit: c5ffd67cd212b6455436315d5f569742eb1da2e3
2025-07-22 17:56:33 -07:00
ylink-lfs 38a24e68ed chore: residual boolptr and intptr removal
Kubernetes-commit: b070b0a5c5ff3d6e356852c67c8ef325aa750daa
2025-07-23 00:57:50 +08:00
Kubernetes Publisher c04562bf9e Merge pull request #133112 from yongruilin/master_vg-cleanup
chore(validation-gen): cleanup and fix for comments and doc

Kubernetes-commit: 405edb50ca70b733ff9b4d71636943d2978e9f26
2025-07-21 18:08:32 -07:00
yongruilin c91403d681 fix(validation-gen): correct typos in comments and documentation
Kubernetes-commit: 8b558a1bc3e29610dd53a46291dc94b00da0573d
2025-07-21 21:24:45 +00:00
Kubernetes Publisher b92abb2d81 Merge pull request #133008 from aaron-prindle/master-pr-vg-union-tags-and-item-tag-chaining-support
feat(validation-gen): Add union validation rule tags and enable +k8s:item chaining to union tags

Kubernetes-commit: 34b9a9958dc269a09bb6ad6dc992712bd2a51d25
2025-07-17 21:02:44 +00:00
Kubernetes Publisher 9b71f293aa Merge pull request #133020 from pohly/apimachinery-list-map-keys
support optional listMapKeys in server-side apply

Kubernetes-commit: d33af7f7efc7ff5d8813a85290189883167a5226
2025-07-17 17:02:43 +00:00
Aaron Prindle c58c5e474b add validate/zeroorone_test.go and add +k8s:zeroOrOneOfMember output tests
Kubernetes-commit: be72d963b887139619bfd404c507ef9d92cf8914
2025-07-16 22:00:31 +00:00
Aaron Prindle ada13e71d1 feat(validation-gen): add +k8s:zeroOrOneOfMember tag validator and associated validate method
Kubernetes-commit: 10b20852e3c5e424534cdd0a9eda611a1dde9b9a
2025-07-16 21:59:57 +00:00
Aaron Prindle d9c68830cd add validate/union_test.go and add +k8s:unionMember and +k8s:unionDiscriminator output tests
Kubernetes-commit: 81f18759e6eabb4b1e14c91a895118176e5d4866
2025-07-16 21:59:24 +00:00
Aaron Prindle 76e5596af4 feat(validation-gen): add +k8s:unionMember and +k8s:unionDiscriminator tag validators and associated validate methods
Kubernetes-commit: 5bc9b69114102e10df03f30552c93caa44d69622
2025-07-16 21:57:30 +00:00
Ben Luddy fa4b7ae54f KEP-4222: Adopt text and JSON transcoding support for CBOR.
Kubernetes-commit: 0b60c12194766e1473e65790369d5a8bcee537c6
2025-07-11 13:23:36 -04:00
Patrick Ohly 3d4cbfbb07 SSA: test optional map keys
As of structured-merge-diff v6.3.0, list map keys may be optional, as long as
at least one key is provided.

Kubernetes-commit: a1a85ddb161c33adaabf6758f8787f86951df601
2025-07-08 11:58:24 +02:00
Jordan Liggitt 2e350ba61b sigs.k8s.io/structured-merge-diff/v6 v6.3.0
Kubernetes-commit: 4d34975a46658efd90274c5fe05d46732e04ca67
2025-07-16 16:57:41 -04:00
Patrick Ohly 2ee6665f26 SSA: add integration tests
test/integration/apiserver/apply covers the behavior of server-side-apply (SSA)
for official APIs. But there seem to be no integration tests which cover the
semantic of SSA like adding/removing/updating entries in a list map. This adds
such a test.

It needs an API which is under control of the test and uses
k8s.io/apimachinery/pkg/apis/testapigroup for that purpose, with some issues
fixed (OpenAPI code generation complained) and a new list map added.

Registering that API group in the apiserver needs a REST storage and
strategy. The API group only gets added in the test. However, the production
code has to know about it. In particular,
pkg/generated/openapi/zz_generated.openapi.go has to describe it.

Kubernetes-commit: 3357e8fc057168fef06b08eaef696511502dcafd
2025-07-08 10:36:53 +02:00
Kubernetes Publisher a75d3d8a0f Merge pull request #132979 from ylink-lfs/chore/residual_intptr_removal
chore: residual intptr removal with ptr.To

Kubernetes-commit: 2fdba619aba197381fd284426e1a1bd4022c3e23
2025-07-16 21:02:36 +00:00
ylink-lfs 3130ae84d8 chore: residual intptr removal with ptr.To
Kubernetes-commit: 5b4c1872a0fd628c53cb8978bbc6ed61bbd5674b
2025-07-16 09:07:25 +08:00
Kubernetes Publisher 1ebcba2516 Merge pull request #132871 from dims/bump-k8s.io/kube-openapi-to-latest-SHA-f3f2b991d03b
Bump k8s.io/kube-openapi to latest SHA (f3f2b991d03b) and sigs.k8s.io/structured-merge-diff/{v4 => v6}

Kubernetes-commit: 48e04d0d6c43cfc4729857775252b89b68d65b87
2025-07-15 09:02:35 +00:00
Kubernetes Publisher 8b4e5d086b Merge pull request #132513 from xiaoweim/validation-cleanup-invalid
Cleanup: Remove field name from invalid field detail message

Kubernetes-commit: 9f97857669c94332f2695c6216885937e4e9556d
2025-07-15 05:02:28 +00:00
Kubernetes Publisher 3b00b62c4b Merge pull request #132934 from ylink-lfs/chore/residual_strptr_removal
chore: residual strPtr utility removal with ptr.To

Kubernetes-commit: 63da3857486aa9b8e36ed06c8ba9aec337bf41d9
2025-07-14 16:42:53 -07:00
ylink-lfs c73d18dbc8 chore: residual strPtr utility removal with ptr.To
Kubernetes-commit: ad03cb87336bfdfd7fd994de697763e1390e2ca2
2025-07-15 00:31:20 +08:00
Kubernetes Publisher d5a1ab82b5 Merge pull request #132907 from PatrickLaabs/132749-boolPtrFn
chore: removed boolPtrFn helpers with ptr package implementation

Kubernetes-commit: 07f3e2f01b715549bf93a8b4ee08642052cae6f9
2025-07-13 05:02:20 +00:00
Kubernetes Publisher a3a3a5d4bd Merge pull request #132469 from yongruilin/master_vg_ratcheting-list
feat(validation-gen): Enhance validation with new rules and core refactoring

Kubernetes-commit: 2b7de3ba74a9706abdc82cbe4573f9266487df14
2025-07-12 20:04:21 -07:00
PatrickLaabs ba86525683 chore: removed boolPtrFn helpers with ptr package implementation
Kubernetes-commit: ba45e37b24bf378c2b4d0a7dec059d35b6774883
2025-07-12 11:35:07 +02:00
Davanum Srinivas 4962c2625e Bump k8s.io/kube-openapi to latest SHA (f3f2b991d03b)
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: ebc1ccc491c944fa0633f147698e0dc02675051d
2025-07-10 09:21:52 -04:00
xiaoweim 5937fdff41 Cleanup: Remove field name from invalid field detail message
Kubernetes-commit: 61542e7a98760726736e48897e0fab85e8ad443a
2025-06-24 19:55:28 +00:00
Ben Luddy b025858346 Bump to github.com/fxamacker/cbor/v2 v2.9.0.
Kubernetes-commit: 917659269af60f8ca960deeb0991df93e5ad1635
2025-06-24 14:25:43 -04:00
Aaron Prindle c904feb835 feat: add +k8s:neq tag which enforces field is neq to a specified comparable value
Kubernetes-commit: fc1c832c4955c77c242a0afff2dfb75110cf004d
2025-06-16 16:06:55 +00:00
yongruilin e6bf2975f4 feat(validation-gen): add k8s:item
- Added new validation functions for items in slices and maps, allowing for flexible matching and validation based on specified criteria.
- Introduced `SliceItem` function to handle item validation logic, including support for matching and validating based on unique identifiers.

Co-authored-by: Aaron Prindle <aprindle@google.com>

Kubernetes-commit: 5cc2721f6c6913644f7ed051d0e522145153933a
2025-07-12 04:15:23 +00:00
yongruilin c904af427c feat: Add validation ratcheting for subfields tag
Kubernetes-commit: af05aa61d9cf9a1f6fdfa130512b9b1db1b08860
2025-06-04 23:20:50 +00:00
yongruilin 3bd79837f5 chore: improve error rendering and add unit tests for ErrorMatcher
Kubernetes-commit: daef13ecc36554d4d1b6b3efeb235820d7a3ad22
2025-07-12 04:11:41 +00:00
yongruilin b31e2b6043 feat(validation-gen): enhance validation functions for slices and maps
- Introduced MatchFunc type for flexible comparison in EachSliceVal and EachMapVal.
- Support ratcheting for list(eachSliceVal)
- Support ratcheting for map(eachMapVal)
- Added new test cases for various comparable and non-comparable structures.
- Improved error handling and validation checks in the validation generation process.

Co-authored-by: Tim Hockin <thockin@google.com>
Co-authored-by: Aaron Prindle <aprindle@google.com>

Kubernetes-commit: b059bb55143149c8d08e5ca2e3e3287dc0477648
2025-07-12 04:08:36 +00:00
Kubernetes Publisher ed63805e81 Merge pull request #132823 from yongruilin/master_vg_enum
feat(validation-gen): add k8s:enum validators

Kubernetes-commit: bb415b6933d31355276efac2edb71d70c0314036
2025-07-09 17:53:35 -07:00
yongruilin 882f988ee5 feat(validation-gen): add Enum validator function
Kubernetes-commit: 345641f106bc174b55d40d87bd9a0a8a78f10e6c
2025-07-08 18:37:42 +00:00
Kubernetes Publisher b18bb6a9e8 Merge pull request #132792 from ylink-lfs/chore/typo_invaILd
chore: typo 'invaILd' occurrence replacement

Kubernetes-commit: 7948fec34bad799f47a644023cdfefa26bb30684
2025-07-08 05:02:02 +00:00
Kubernetes Publisher cd1624cbbf Merge pull request #132794 from PatrickLaabs/132749-boolptr
chore: replacement of boolPtr helper functions to ptr packge

Kubernetes-commit: 5f34f9233b2e2f9714b71dc1e1a0dbf24548866c
2025-07-07 20:13:26 -07:00
PatrickLaabs f90b6830ca chore: replacement of helper functions to ptr packge
Kubernetes-commit: cfd65c5f74dbb5fa02e7b20c3b1039193567ba53
2025-07-07 21:28:54 +02:00
ylink-lfs e76ac9371e chore: typo invaILd occurrence replacement
Kubernetes-commit: d9de37d9316e25cad5bea7ab8694b64bf76b43e4
2025-07-08 00:08:34 +08:00
Kubernetes Publisher a9de165b70 Merge pull request #132314 from thockin/jp_nicer_api_errors
Nicer value rendering in API errors

Kubernetes-commit: 2a4b5f64761f1578409c3f69ef7aaab614cdd907
2025-07-03 09:01:49 +00:00
Kubernetes Publisher b86b632271 Merge pull request #132675 from dims/bump-sigs-k8s-io-json-no-code-changes
Bump sigs.k8s.io/json to latest - no code changes

Kubernetes-commit: e47ac3eb6faa97874658dc281c72b5623f994801
2025-07-03 01:01:50 +00:00
Kubernetes Publisher 7548d4da2f Merge pull request #132676 from dims/bump-go.yaml.in/yaml/v3-to-v3.0.4
Bump go.yaml.in/yaml/v3 to v3.0.4

Kubernetes-commit: 01c03ae9cf7b1371c8bc2bdf12d9244e63e83750
2025-07-02 17:01:56 +00:00
Davanum Srinivas 45e4ebb75e Bump go.yaml.in/yaml/v3 to v3.0.4
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 58e620cc4403d30f9fb6aab245cfb47db17957de
2025-07-02 07:37:06 -04:00
Davanum Srinivas a864156cb7 Bump sigs.k8s.io/json to latest - no code changes
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 00f8cbae6b8fd3799a1a044abcefdbb572d35b27
2025-07-02 07:32:24 -04:00
Kubernetes Publisher 852f12619b Merge pull request #132654 from Jefftree/b-openapi
Bump kube-openapi

Kubernetes-commit: db49c25956df36c777213251c4a47d6d9ee1c5ea
2025-07-01 21:01:44 +00:00
Kubernetes Publisher 1880ea593e Merge pull request #132585 from jpbetz/drop-openapi-name-dependant-test
Drop test that checks openAPI resource that checks OpenAPI resource name

Kubernetes-commit: e140f056dc861754c966560638d09021a1f351f9
2025-07-01 17:01:51 +00:00
Jefftree 7f21f07c1c Update vendor
Kubernetes-commit: d04ee27c98ba91680ac6c6a8ade9e33d7ee44569
2025-07-01 15:23:58 +00:00
Jefftree 48f740b63c pin kube-openapi to v0.0.0-20250628140032-d90c4fd18f59
Kubernetes-commit: b41d375b8881f25ff5fe7775b4dedaba1eaa3f02
2025-07-01 15:21:22 +00:00
Kubernetes Publisher 10cef9d38d Merge pull request #132472 from xiaoweim/validation-cleanup
Cleanup: Remove redundant detail messages in field.Required

Kubernetes-commit: eb1b603cda3b956e52bddf3b51748191e80a59a6
2025-07-01 09:22:56 +00:00
Kubernetes Publisher 6af1141f10 Merge pull request #132465 from yongruilin/master_vg_fix-fuzz-test
fix: versioned validation test avoid incorrect conversion

Kubernetes-commit: bc9a78479fb7d5317ee2d5c8ac95acc0d1661272
2025-06-30 20:58:29 -07:00
Joe Betz b0db85e7d4 Drop test that checks openAPI resource name since we currently don't guarantee name stability in the API
Kubernetes-commit: f93b4408a77a66fbeccfff99406b0dea10991e79
2025-06-27 15:02:34 -04:00
xiaoweim cfeb5db0a7 Cleanup: Remove redundant detail messages in field.Required
Kubernetes-commit: 8632257c9340aeae824c99642376a78f69b3ea5d
2025-06-26 19:41:17 +00:00
yongruilin 5c20c365a1 fix: versioned validation test avoid incorrect conversion
Kubernetes-commit: a55318fe1493728ea0f6752375f3e41d821b4d0d
2025-06-18 05:43:31 +00:00
Kubernetes Publisher d6651abdfe Merge pull request #132357 from dims/drop-usage-of-forked-copies-of-goyaml.v2-and-goyaml.v3
Drop usage of forked copies of goyaml.v2 and goyaml.v3

Kubernetes-commit: c1afec6a0b15ca1ed853c1321ac2c972488bf5b8
2025-06-25 17:22:36 +00:00
Kubernetes Publisher f3d86859ab Merge pull request #132504 from jpbetz/name-formats
Introduce OpenAPI format support for k8s-short-name and k8s-long-name

Kubernetes-commit: 1d932bd6cc951b9182d07d701946aebaf667df94
2025-06-25 17:22:35 +00:00
Davanum Srinivas e6ac37c004 switch to latest sigs.k8s.io/yaml v1.5.0 (run update-gofmt.sh as well)
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: c5b4b133ce3252ee19b7167eb69a99d88fdefda8
2025-06-25 08:03:06 -04:00
Kubernetes Publisher c21f374a5e Merge pull request #132255 from likakuli/feat-nothingfastreturn
feat: optimize ListAll and ListAllByNamespace to return directly when nothing to select

Kubernetes-commit: c379296bd1b29ee8e890aff393959398ac905b2f
2025-06-24 17:22:42 +00:00
Kubernetes Publisher f545e563a3 Merge pull request #132376 from tico88612/cleanup/message-count-map
apimachinery/pkg/util/errors: deprecated MessageCountMap

Kubernetes-commit: 885db534cabb9c1975553f43e32c90f0be256a51
2025-06-24 17:22:41 +00:00
Joe Betz 5f2744fe1e Bump to latest kube-openapi
Kubernetes-commit: dc323756cea2d1ebe32d7acb5a14a1769c14486f
2025-06-24 09:24:27 -04:00
Kubernetes Publisher ae7698643b Merge pull request #132194 from alvaroaleman/local0dev
Add an interface that all apply configurations implement

Kubernetes-commit: 7b04d7faefcc28a90fb092e789abda467b086403
2025-06-18 15:36:50 -07:00
ChengHao Yang 36c0fecd85 apimachinery/pkg/util/errors: deprecated MessageCountMap
Signed-off-by: ChengHao Yang <17496418+tico88612@users.noreply.github.com>

Kubernetes-commit: df32f10e0695ea49bf4d99b4fdc4531c87192c73
2025-06-19 01:23:10 +08:00
Tim Hockin 7fecabd51e Don't panic in case of an unknown API error code
Kubernetes-commit: e68d601344965e67fc09da9d7e4979c4bf72c9c3
2025-06-14 18:12:36 -07:00
Tim Hockin 0084b28976 WIP: Fix tests
Notes:
* For types that define String() - should we prefer that or JSON?
* metav1.Time has a MarshalJSON() and inhereits a String() and they are
  different
* Since validation runs on internal types, we still get some GoNames
  instead of goNames.

Kubernetes-commit: 4ca91a03052ebf31d373a0de6e12891ae15966b9
2025-06-18 10:23:01 +09:00
Tim Hockin c58e197ee8 Nicer value rendering in API errors
Today, if the value passed is a struct, map, or list, we get Go's vative
rendering which is clunky.

This uses JSON (could be kyaml when that is ready) instead.

I hear it already: "But JSON is slow!".  I benchmarked it -- for an
simple int or string field, JSON is only a little slower (~20%) than a
type assertion, but it IS slower, so I left the type assertion in.
Remember that this is only called when an API error has occurred.

The type assertions do not handle typedefs-to{string, int64, etc} so
those will fall back on JSON.  Almost all of our errors go thru standard
functions which demand string or int64 anyway, so mostly pointless.

I also benchmarked using reflect to check `CanInt()` and that is almost
exactly as fast as type-switch but handles more cases, so we COULD
switch to that instead, if we wanted. I thought it wasn't worth the
complexity.

JSON is really there to handle composite types.

Kubernetes-commit: 2b2c9adef385c064c04bf456fa483dbad742a048
2025-06-14 17:53:02 -07:00
likakuli d3c271abfa feat: optimize ListAll and ListAllByNamespace to return directly when nothing to select
Signed-off-by: likakuli <1154584512@qq.com>

Kubernetes-commit: 5a7e04b6cc70bd7624536e09b4e977e8f25cb476
2025-06-12 17:33:32 +08:00
Alvaro Aleman 07890ccffb Add an interface that all applyconfigs implement
Kubernetes-commit: 3fe4ea550e8267d11ab2825be3770678bf868e37
2025-05-28 15:22:17 -04:00
Kubernetes Publisher 1025cde0fd Merge pull request #132349 from p0lyn0mial/upstream-rm-initial-events-list-blueprint
apimachinery/meta/types.go: remove InitialEventsListBlueprintAnnotationKey

Kubernetes-commit: 70fc3f2bba771248054ce97807e986e3fc5311c1
2025-06-17 13:30:59 -07:00
Lukasz Szaszkiewicz 47b0b24556 apimachinery/meta/types.go: remove InitialEventsListBlueprintAnnotationKey const
Kubernetes-commit: 15ca38b521d94abfac00fcf57031fcaa28b8d7df
2025-06-16 15:29:10 +02:00
Kubernetes Publisher d2d60b8eb2 Merge pull request #132221 from dims/new-cmp-diff-impl
New implementation for `Diff` (drop in replacement for `cmp.Diff`)

Kubernetes-commit: 3e39d1074fc717a883aaf57b966dd7a06dfca2ec
2025-06-17 03:24:54 +00:00
Davanum Srinivas 6c33c81b53 Add a replacement for cmp.Diff using json+go-difflib
Co-authored-by: Jordan Liggitt <jordan@liggitt.net>
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 03afe6471bdbf6462b7035fdaae5aa0dd9545396
2025-06-10 23:08:41 -04:00
Kubernetes Publisher e0270fe44c Merge pull request #132269 from dims/update-to-latest-github.com/modern-go/reflect2
Update to latest github.com/modern-go/reflect2

Kubernetes-commit: d55b119d34883bbad2a3436dcb6c62339d963031
2025-06-12 19:54:03 +00:00
Kubernetes Publisher 01cb050342 Merge pull request #132232 from likakuli/feat-optlabelselector
feat: optimize label selector match performance

Kubernetes-commit: e33933fa977d876df1093d6b893ec4c0b443f7e9
2025-06-12 19:54:02 +00:00
Davanum Srinivas 9dd84ae6f3 Update to latest github.com/modern-go/reflect2
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 3908550c0dc189cfa9de38a84bee508fa0659463
2025-06-12 11:20:39 -04:00
Kubernetes Publisher 9ad036216e Merge pull request #132135 from jpbetz/options-to-slice-master
Simplify options in declarative validation

Kubernetes-commit: c06a41b31667e83cbdd19734d5716775f9c3937e
2025-06-12 07:54:05 +00:00
likakuli e172d59e75 feat: optimize label selector match performance
Signed-off-by: likakuli <1154584512@qq.com>

Kubernetes-commit: e75ccce83fcb4323f480581644a5933f3d9cf2f6
2025-06-11 21:20:32 +08:00
Kubernetes Publisher a1eb4c90eb Merge pull request #132217 from yongruilin/master_vg_refactor-test
feat(validation-gen): Improve validation test helpers for validation-gen

Kubernetes-commit: 8052df02af256b79f31c65ee0430a7373a6f38b4
2025-06-10 23:36:55 -07:00
Tim Hockin e22a1dfe78 Better formatting of matcher errors
Kubernetes-commit: 28e973c04472bc31097d1483db97e3344e9cc764
2025-04-14 09:51:47 -07:00
Kubernetes Publisher d5745c2f38 Merge pull request #132103 from nojnhuh/typed-ring-buffer
Replace queue.FIFOs with k8s.io/utils/buffer.Ring

Kubernetes-commit: 5090812df4fb6cf09a9181635d90c2e154eab8cc
2025-06-06 19:54:23 +00:00
Kubernetes Publisher 1ea980407b Merge pull request #132034 from ChosenFoam/dev-fix
Fix the IsDNS1123SubdomainWithUnderscore function to return the correct error message

Kubernetes-commit: 2d3ca18f645edd9065c32f07f02de2ba6273ab1d
2025-06-06 03:54:17 +00:00
Kubernetes Publisher 90f1d78adf Merge pull request #132110 from jpbetz/gengo-bump
Bump gengo/v2 to latest, pick up related validation-gen fixes

Kubernetes-commit: 4fff091ce7c8b22e6a511231e400adb865a7b300
2025-06-05 11:58:45 -07:00
Jon Huhn e15237071b Update k8s.io/utils for new generic ring buffer
Kubernetes-commit: 8cdbbf5cdaef7e37cfd432e9044aa52f4d42adcd
2025-06-04 12:09:53 -05:00
Tim Hockin 4af193d7f5 Fix field path for embedded fields in root types
Given the following:

```
type Struct type {
    TypeMeta int

    // +k8s:validateFalse="embedded"
    OtherStruct
}
```

What should the field-path be for the error?

or:

```
type Struct type {
    TypeMeta int

    OtherStruct // embedded
}

// +k8s:validateFalse="otherstruct"
type OtherStruct {
  I int
}
```

Conclusion: If we find an embedded field, we look for a nil fldPath, and
if so we set it to the type name.  Embedded values in root types (e.g.
above `spec`) should not be a things we actually do.

Implementation: Add `safe.Value(*T, func() *T)`

Kubernetes-commit: 2c4c3037b6a5dbcd9dcc739607ac8580571d3d09
2025-06-03 17:39:05 -07:00
Kubernetes Publisher a925cd7fb3 Merge pull request #132039 from alvaroaleman/fix-w-unstructured
FieldManagedObjectTracker: Fix to work with unstructured

Kubernetes-commit: fd53f7292c7d5899135fddd928c0dc3844126820
2025-06-02 15:04:42 -07:00
huyurui17 855a9556cb add unit test for IsDNS1123SubdomainWithUnderscore function
Kubernetes-commit: 899f76159f130dba4fdf93d374884d8b8c704487
2025-06-01 00:48:16 +08:00
Alvaro Aleman 251918081b FieldManagedObjectTracker: Fix to work with unstructured
Prior to this patch, this fails because the skipnonappliedfieldmanager
uses the `objectcreater` (aka `*runtime.Scheme`) to create a new object
for which it never sets the GVK. In the case of
`*unstructured.Unstructured`, the GVK can not be derived from the object
itself so the operation would subsequently fail [here][0] with an
```
Object 'Kind' is missing in 'unstructured object has no kind'
```

error. Fix this by explicitly setting the GVK in case of unstructured.

[0]: 02eb7d424a/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/structuredmerge.go (L98)

Kubernetes-commit: dbdd6a3b4358d91a064de9c0f01d3050e606d553
2025-05-30 15:30:33 -04:00
huyurui17 add5cf846c Fix IsDNS1123SubdomainWithUnderscore to return correct error message
Kubernetes-commit: be049397dfd0752f90ec85eef938ab54d04d005c
2025-05-31 00:14:07 +08:00
Kubernetes Publisher 09ff13941c Merge pull request #131664 from jpbetz/subresources-enable-replicas
Migrate to declarative validation: ReplicationController/scale spec.replicas field

Kubernetes-commit: 144926558984ae41a7328d53bd9fc8602328f10e
2025-05-27 09:14:16 -07:00
Joe Betz cb4ef17d4e Simplify subresource matching
Kubernetes-commit: 8ae7171041bb1a24d9436e3f576fced4aa604b79
2025-05-23 19:30:09 -04:00
Joe Betz dc0ccb55f8 Clarify errors and improve tests
Kubernetes-commit: 9715c90b31ccc121ba910044c03ecb9936b1f858
2025-05-19 14:12:09 -04:00
Joe Betz 52e0cea356 Update testing to fully track subresources
Kubernetes-commit: 7dc8660d03635a3cd61ae6db8c586acc90ddba97
2025-05-19 11:20:46 -04:00
Joe Betz 460ba78fbe Add +k8s:isSubresource and +k8s:supportsSubresource tags
Kubernetes-commit: 6ca6b7bb6ab7865ace03601c5d34571196126c8b
2025-05-19 11:18:06 -04:00
Kubernetes Publisher da3bba9054 Merge pull request #128419 from liggitt/etcd-3.6
etcd 3.6 client update

Kubernetes-commit: 09ca440a450e9103a8f835f598c09237dba6ecbb
2025-05-16 03:29:56 +00:00
Jordan Liggitt 0d18dbd72a bump etcd client to 3.6
hack/pin-dependency.sh go.etcd.io/etcd/api/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/client/pkg/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/client/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/pkg/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/server/v3 v3.6.0

hack/pin-dependency.sh github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0

hack/update-vendor.sh

Kubernetes-commit: cf0bbf1171e918d5d7ba1d3c83b5f347fc8333b0
2025-05-15 21:19:11 -04:00
Kubernetes Publisher b482a2c0d9 Merge pull request #131706 from karlkfi/karl-close-watch-tests
test: Close response body in watch tests

Kubernetes-commit: 5f1a5174c508f6fad9d6c06dc8b5675ef04b9a03
2025-05-15 19:12:48 +00:00
Kubernetes Publisher 1bb0bf9dfa Merge pull request #131715 from thockin/kk_yaml_decoder_nits
Account consumed newlines properly in YAML decoder

Kubernetes-commit: 1a0eeb90e46a97ae296282b907c3362380aedb13
2025-05-12 16:01:15 -07:00
Tim Hockin f4fdaa0c9a Account consumed newlines properly in YAML decoder
This isn't a significant functional bug in that the StreamDecoder is
still offset to account for it (which is why we never saw it before).
But it's a trap for future use-cases.

Also move one LOC to match a parallel-shaped block above.

Kubernetes-commit: cfe7d0424356f2e75660525d100617eae45f09a8
2025-05-11 16:16:46 -07:00
Kubernetes Publisher 202cba0f14 Merge pull request #131702 from tigrato/master
Panic in `NewYAMLToJSONDecoder`

Kubernetes-commit: 2fed387a900bf8004fde4e577cd8b77c93aeca1f
2025-05-09 15:41:18 -07:00
Tiago Silva a49178112c fix: fixes a possible panic in `NewYAMLToJSONDecoder`
This PR fixes a possible panic caused by decoding a JSON document
followed by a YAML document that is shorter than the first json
document.

This can cause a panic because the stream already consumed the JSON
data. When we fallback to YAML reader, the YAML starts with a zero
offset while the stream consumed data is non-zero. This could lead into
consuming negative bytes because `d.yaml.InputOffset() -
d.stream.Consumed()` is negative which will cause a panic.

Signed-off-by: Tiago Silva <tiago.silva@goteleport.com>

Kubernetes-commit: d1fb42a4000333ced2b2b26ac65fe64958a4259e
2025-05-09 18:27:20 +01:00
Kubernetes Publisher f7c4380031 Merge pull request #131678 from karlkfi/karl-api-methods
refactor: Use http method constants in apimachinery

Kubernetes-commit: ebb13a2ad7a7a9a8c62b7e56bbb88f85c208cea6
2025-05-09 00:31:28 -07:00
Karl Isenberg c4b7aee3fc refactor: Use http method constants in apimachinery
- Linter catches this sometimes, but this change catches a few more.
- Avoids using string literals when standard constants are available.

Kubernetes-commit: 3f188e5d86b35993c79d23dbcdfb264af0141241
2025-05-08 12:54:06 -07:00
Kubernetes Publisher d56afd172a Merge pull request #131616 from jpbetz/typeconverter-cleanup
Reorganize scheme type converter into apimachinery utils

Kubernetes-commit: 7cb2bd78b22c4ac8d9a401920fbcf7e2b240522d
2025-05-08 03:11:40 +00:00
Kubernetes Publisher 37c3ecc10a Merge pull request #131635 from thockin/kk_cleanup_codegen
Accumulated cleanups of validation-gen

Kubernetes-commit: 51fff4f8bdb7c39988dd1661bc6252fbd671f0eb
2025-05-07 19:11:35 +00:00
Joe Betz 0a090da87e Reorganize scheme type converter into apimachinery utils
This removes a dependency from generated applyconfigurations to a testing
package. To do this, the type converter in the testing package has been
moved out to the apimachinery package and the utilities the converter
depend on have been reorganized.

Kubernetes-commit: 4821604f83a6f4764497879b666087ba7cb05060
2025-05-07 10:07:55 -04:00
Kubernetes Publisher 2b45e0daa3 Merge pull request #131443 from jpbetz/lockfree-error-backoff
Avoid lock contention for error backoff

Kubernetes-commit: 1cc003ae4880c040e3ec47f5afcb0bf013d6a0e3
2025-05-06 23:12:03 +00:00
Joe Betz e303f8a95d Fix rudimentaryErrorBackoff to only be created once
Kubernetes-commit: c5efc843daca2997104e617a3dda204dddef0f2c
2025-05-06 15:56:29 -04:00
Kubernetes Publisher e07849993d Merge pull request #131560 from jpbetz/validation-gen-subresource-simplification
Declarative validation: Simplify handling of subresources

Kubernetes-commit: c6739dd54d56549a8460737f66b1c6aa0fa697bf
2025-05-06 19:11:57 +00:00
Kubernetes Publisher 863c50fec7 Merge pull request #131399 from thockin/kk_realType
Fixes for bad handling of pointers and aliases in validation

Kubernetes-commit: 07704ee9b1ca4c3b4048e99f171c3736bf57584a
2025-05-06 03:12:21 +00:00
Kubernetes Publisher 512f488de3 Merge pull request #131595 from aojea/utils_fake_clock
update k8s.io/utils to bring fakeClock.Waiters()

Kubernetes-commit: e3e1f80c0110c847acf4381b1790c1c667395010
2025-05-03 03:11:11 +00:00
Tim Hockin 126acd266c Fix typo in comment
Kubernetes-commit: 97e64e80c3e5ac98c6d123401f4d9ab1393c89aa
2025-05-02 10:48:33 -07:00
Antonio Ojea 5b059a2317 update k8s.io/utils to bring fakeClock.Waiters()
Change-Id: I7e25338df225c2c27457403fbc2f158d08638f87

Kubernetes-commit: c2c003a71fc52fa79c2fff0109afad58573d0216
2025-05-02 11:21:11 +00:00
Kubernetes Publisher 7b819a3b82 Merge pull request #130989 from liggitt/creationTimestamp-omitzero
Omit null creationTimestamp

Kubernetes-commit: 01899a7c86337b05a16a4155c9351cf947beaee9
2025-05-02 23:11:39 +00:00
Karl Isenberg b9dc8ce33c test: Close response body in watch tests
- Close response body after http calls in the watch tests
- Add apitesting utility methods for closing and testing errors
  from response body and websocket.
- Validate read after close errors.

Kubernetes-commit: 9d963298a3b7b828f01a9b02af57863a7480eb0b
2025-04-29 17:32:48 -07:00
Tim Hockin 1feb0afd7e Fix immutable validation for structs with pointers
Comparing pointers is not the same as deep-equal.  This was an
embarassing oversight.

Kubernetes-commit: 9d519c7c46e5e064daa8a4098d238775802746c3
2025-04-23 19:27:12 -07:00
Joe Betz f639733311 Rewrite Subresources godoc.
Kubernetes-commit: 2e8b409a5ffa3791bafaceafef9570606f137825
2025-04-14 14:42:34 -04:00
Joe Betz 4390983008 Change option to a slice
Kubernetes-commit: 501393810064f96808eba093ae5c00780020c667
2025-03-29 08:59:30 -04:00
Joe Betz e514dfafab Use slice.Contains()
Kubernetes-commit: 990cb7547ca3235d245d31b1a1e4a8db7abeee84
2025-04-14 14:17:15 -04:00
Joe Betz 43d4698fb9 Add subresource to operation, do not special case subresources in validation-gen
Kubernetes-commit: 2119555e02b357db58e460cd8f38bf187b5f837b
2025-03-26 21:26:14 -04:00
Jordan Liggitt 9386fa7b71 Drop null creationTimestamp from test fixtures
Kubernetes-commit: 6bb6c9934294d8265197c9dfc4c9dd3adaca147a
2025-03-24 09:37:26 -04:00
Jordan Liggitt 4a2766af85 Update runtime convertor to honor IsZero()
Kubernetes-commit: 41805aff9158b976f32611d36812215257c35707
2025-03-24 09:37:00 -04:00
Jordan Liggitt b5d3fcfd1f bump cbor to add omitzero support
Kubernetes-commit: bc6051717137cef288b82305588e675de4a32c0d
2025-03-25 12:27:43 -04:00
Jordan Liggitt 0aaa0d40ad bump structured-merge-diff to add omitzero support
Kubernetes-commit: 06b0784062f68566daa8eed83c475b738dcf620c
2025-03-24 16:34:01 -04:00
Jordan Liggitt 503b989732 Omit null metadata.creationTimestamp
Kubernetes-commit: fdf0bb41a4488057b6f44dc6aa456f0e977ae2be
2025-03-21 14:57:11 -04:00
Kubernetes Publisher 70566753bf Merge pull request #131029 from liggitt/to-unstructured-stdlib
Make ToUnstructured match stdlib omitempty and anonymous behavior

Kubernetes-commit: ef239ae62b86ee03b8b5e7a01d7c4cf4dcdd4a92
2025-05-02 08:07:55 -07:00
Jordan Liggitt a0fdacfb32 Make ToUnstructured match stdlib omitempty and anonymous behavior
Kubernetes-commit: 09912f3521932db99fe19f993db240fd43f3240e
2025-03-24 11:52:44 -04:00
Kubernetes Publisher 9549609199 Merge pull request #130995 from xigang/utils
bump k8s.io/utils for improvements

Kubernetes-commit: 43a7d3be12425cc80ca6ad3599809a19728c5566
2025-04-23 23:15:24 +00:00
Kubernetes Publisher 27a82ebc8b Merge pull request #130994 from BenTheElder/host-network-no-port
Remove inaccurate doc comment from podspec hostNetwork field

Kubernetes-commit: b775f9b92f98f7b3acbb3864ed53c2f5b835e917
2025-04-23 23:15:22 +00:00
Kubernetes Publisher 7b4292bd2e Merge pull request #131103 from ahrtr/etcd_sdk_20250328
Bump etcd 3.5.21 sdk

Kubernetes-commit: f4d1686120d2367dd4c00df53e93dad51c414435
2025-04-01 10:18:05 +00:00
Benjamin Wang 90c4280d8b bump etcd 3.5.21 sdk
Signed-off-by: Benjamin Wang <benjamin.ahrtr@gmail.com>

Kubernetes-commit: f3b80a858225178e3f7a3ae07bd1b9894e7b3456
2025-03-28 14:30:47 +00:00
xigang da1e88666e bump k8s.io/utils
Kubernetes-commit: fe14689f221a968806b771b226581efb834654cd
2025-03-22 10:14:01 +08:00
Benjamin Elder 89b3b23b4b make update
Kubernetes-commit: 1c3dc397ae137cc8b1d2095ea33217a239b81b55
2025-03-21 18:19:43 -07:00
Benjamin Elder 688a8fd9d9 remove inaccurate hostNetwork doc comment
also remove from copies in example / test APIs

Kubernetes-commit: 8af1629f7aeeabeaec21f3fbcee5bc60d9ad2015
2025-03-21 18:19:29 -07:00
Kubernetes Publisher e8a77bd768 Merge pull request #130910 from googs1025/fix/datarace
flake: fix data race for func TestBackoff_Step

Kubernetes-commit: 3a14b619d5862b058f941e07f381e8965c5c7702
2025-03-19 09:28:00 +00:00
Kubernetes Publisher 7e8c77e774 Merge pull request #130906 from serathius/streaming-validation
Update kube-openapi and integrate streaming tags validation

Kubernetes-commit: 32b1819423de505da855cf7544e871a04e63d6ed
2025-03-19 05:27:58 +00:00
googs1025 27fd3963bb flake: fix data race for func TestBackoff_Step
Kubernetes-commit: 2f1f19a992fcea1aa9f81731296335d1bd4dc533
2025-03-19 09:23:21 +08:00
Marek Siarkowicz 8bcc6f18d0 Update kube-openapi and integrate streaming tags validation
Kubernetes-commit: 75a4d136abac241f728407515e3d0d8305594675
2025-03-18 21:26:22 +01:00
Kubernetes Publisher 6ce776c88d Merge pull request #130857 from thockin/kk_small_vg_diffs
Port small deltas from validation-gen dev branch to master

Kubernetes-commit: abad982cf82f83368297d20faf3f2ecedc2295bf
2025-03-16 15:49:47 -07:00
Tim Hockin f2c94d6c4b Comment on origin and JSON schema
Kubernetes-commit: b47e839e4ecc77e2caad7855c9869517559dfbaf
2025-03-16 14:32:49 -07:00
Tim Hockin b63ba07f71 Use origin in validateFalse's own test
Kubernetes-commit: 4c0c2d21ea6fb2baf18619ac51e50f26e16d4545
2025-03-16 14:27:31 -07:00
Tim Hockin beddba44f4 Use test.Helper in helper funcs
Kubernetes-commit: d1d77cd553c7d43b14a4da453c69606c5413c530
2025-03-16 14:26:41 -07:00
Kubernetes Publisher eaf4038701 Merge pull request #130354 from siyuanfoundation/forward-api
KEP-4330: add forward compatibility for compatibility mode

Kubernetes-commit: 8b08487283d563efa0bc849ac3a3701463bc49bd
2025-03-14 05:27:48 +00:00
Kubernetes Publisher c8bf40462f Merge pull request #130019 from yongruilin/version-intro
KEP-4330: extend version information with more detailed version fields

Kubernetes-commit: 23d63770284cb30a2eb90b79ace0f1c7e32fb16f
2025-03-14 01:27:50 +00:00
Kubernetes Publisher a04ff375ce Merge pull request #122550 from danwinship/tighten-ip-validation
Tighten IP/CIDR validation

Kubernetes-commit: 2261137135631d24248e94dff3bc6375ad9308b2
2025-03-13 01:27:45 +00:00
Kubernetes Publisher 2eee037957 Merge pull request #130705 from aaron-prindle/validation-gen-add-metric-and-runtime-verification-upstream
[Declarative Validation] feat: add declarative validation metrics and associated runtime verification tests

Kubernetes-commit: 21f7eaa8e2b9c1a70b607cc42d0f038a9efc1906
2025-03-12 21:27:45 +00:00
Kubernetes Publisher 2687636770 Merge pull request #130739 from jpbetz/declarative-validation-test-infra
Introduce versioned validation test utilitizes and add fuzz tester

Kubernetes-commit: 7d6700a532d4e51b29d5df2827a17ffcc2f0f9af
2025-03-11 21:37:53 -07:00
Aaron Prindle f33bb5d4eb chore: change error_matcher.go to use test interface instead of importing testing pkg
Kubernetes-commit: cd9df2f115a95835e07cddf740861dbd8f6f3988
2025-03-11 05:24:07 +00:00
yongruilin 18f464253a refactor: detach Info from apimachinery util version
- Remove `info` field from `Version` struct
- Modify `WithInfo` and `Info` methods to be deprecated
- Update version information retrieval to use base version info
- Simplify version information generation in compatibility tests
- Remove unnecessary version info passing in build and test scenarios

Kubernetes-commit: 14934b481ef6522d6c1003ded19002ea45abe5d1
2025-03-05 23:55:08 +00:00
yongruilin a78ae8bc64 feat: extend version information with more detailed version fields
- Add new version fields to version.Info struct:
  * EmulationMajor and EmulationMinor to track emulated version
  * MinCompatibilityMajor and MinCompatibilityMinor for compatibility tracking
- Update related code to populate and use these new fields
- Improve version information documentation and OpenAPI generation
- Modify version routes and documentation to reflect new version information structure

Kubernetes-commit: a3094ccbe6f9f134da29aedf4d6d87a9a97bf463
2025-02-06 16:11:12 -08:00
Siyuan Zhang 39750cdbae Add emulation forward compatibility into api enablement and RemoveDeletedKinds.
Signed-off-by: Siyuan Zhang <sizhang@google.com>

Kubernetes-commit: 819cb8fe22fe37bf691f460bc32d0f03f53cce09
2025-02-04 10:11:56 -08:00
Tim Hockin d9e6c50b14 Introduce versioned validation test utilitizes and add fuzz tester
This makes a bold assumption: that the errors (count and basic content)
will be the same across versions.  If this turns out to be untrue, this
may need to get more sophisticated.  It should fail obviously when we
hit that edge.

Kubernetes-commit: 1d365762a5358e346b3133b369eadd9be6d4c85b
2024-12-11 14:28:15 -08:00
Kubernetes Publisher e79d000292 Merge pull request #129407 from serathius/streaming-proto-list-encoder
Implement streaming proto list encoder

Kubernetes-commit: 1b6e321e2311757a521615917f99dbe8e58f623c
2025-03-12 01:27:45 +00:00
Kubernetes Publisher 87bb4f9a9b Merge pull request #130730 from jpbetz/minimum-tag
Add +k8s:minimum validation tag

Kubernetes-commit: f3a23cfe90b060c3be26f095c4417bc57280c80e
2025-03-11 15:59:46 -07:00
Marek Siarkowicz e0ec8168b4 Implement streaming proto encoding
Kubernetes-commit: f5dd7107f7144c4f76ca6159c1eeddb48a12feaa
2024-12-19 12:30:39 +01:00
Tim Hockin a18d60b0df Add +k8s:minimum validation tag
Kubernetes-commit: d6ef05b9a826ab0cb662efedae095e6b7ae7adff
2024-11-30 14:31:42 -08:00
Kubernetes Publisher 6e3d6ca424 Merge pull request #128786 from danwinship/bad-ip-warnings
warn on bad IPs in objects

Kubernetes-commit: 3782b558a28fc5cc7d3f451fd03cbbb802d12a62
2025-03-11 09:27:30 +00:00
Kubernetes Publisher 56015c7e0a Merge pull request #130699 from thockin/master_validation-gen_odd_cases
Prevent validation-gen usage patterns we don't want to support

Kubernetes-commit: b90ff89ed62b82f21b1253a543e6603aafb1cadd
2025-03-11 01:27:23 +00:00
Kubernetes Publisher 40f26b37da Merge pull request #130695 from yongruilin/validation-gen_coveredbydeclarative
[Declarative Validation] Add CoveredByDeclarative to field error struct

Kubernetes-commit: f5f9484286cbe651f78b5f59f227f5dbd3b2e644
2025-03-10 17:15:46 -07:00
Tim Hockin af97bd6ab1 Prevent usage patterns we don't want to support
* typedefs to pointers
* pointers to pointers
* pointers to lists
* pointers to maps
* fixed-size arrays
* lists of pointers
* lists of lists
* lists of maps
* maps with non-string keys
* maps of pointers
* maps of lists
* maps of maps

Kubernetes-commit: dcbfe67b1ca9f5a9b5ebbb8ed62e09e14a1c65d2
2025-03-05 19:42:20 -08:00
yongruilin 06dde8a0df Add CoveredByDeclarative to field error struct
- Introduce CoveredByDeclarative field to Error struct
- Add MarkCoveredByDeclarative method for Error and ErrorList
- Implement ExtractDeclarative method to filter out declaratively covered errors
- Update error constructors to include the new field
- Add corresponding test cases for new declarative validation functionality

Kubernetes-commit: 8eb90fe136d74dc0a5c891cf1cdc428acdd06dec
2025-03-05 22:09:24 +00:00
Kubernetes Publisher ee322b25b6 Merge pull request #130666 from thockin/yaml_json_ambiguous_decode
Better handling of YAML that tastes like JSON

Kubernetes-commit: 0791d6ef704f2bffb9160b833fdf52bae20df036
2025-03-10 21:27:30 +00:00
Kubernetes Publisher e25aab096b Merge pull request #130555 from thockin/k_k_randfill
Use randfill in k/k

Kubernetes-commit: 0f2bde7745f3b4eadcf317bc5056dfeb96859bd3
2025-03-09 13:28:01 +00:00
Tim Hockin 9b3d085fa5 Better handling of YAML that tastes like JSON
For the most part, JSON is a subset of YAML.  This might lead one to
think that we should ALWAYS use YAML processing.  Unfortunately a JSON
"stream" (as defined by Go's encoding/json and many other places, though
not the JSON spec) is a series of JSON objects.  E.g. This:

```
{}{}{}
```

...is a valid JSON stream.

YAML does NOT accept that, insisting on `---` on a new line between YAML
documents.

Before this commit, YAMLOrJSONDecoder tries to detect if the input is
JSON by looking at the first few characters for "{".  Unfortunately,
some perfectly valid YAML also tastes like that.

After this commit, YAMLOrJSONDecoder will detect a failure to parse as
JSON and instead flip to YAML parsing.  This should handle the ambiguous
YAML.

Once we flip to YAML we never flip back, and once we detect a JSON
stream (as defined above) we lose the ability to flip to YAML.  A
multi-document is either all JSON or all YAML, even if we use the JSON
parser to decode the first object (because JSON is YAML for a single
object).

Kubernetes-commit: 770ce2d874bcb053af5c76ee07b3caeafb212bd2
2025-03-08 15:38:10 -08:00
Tim Hockin a18d7f2679 Vendor randfill
Kubernetes-commit: 0ce4268b1fe4f78d77249e329b0349b9d2dd2c65
2025-03-03 23:46:48 -08:00
Tim Hockin 78ddbb827e Use randfill, do API renames
Kubernetes-commit: e54719bb6674fac228671e0786d19c2cf27b08a3
2025-02-20 09:45:22 -08:00
Kubernetes Publisher ac04c7e419 Merge pull request #130569 from dims/update-to-latest-cadvisor-v0.52.0
Update to latest cadvisor @ v0.52.1 and new opencontainer/cgroups and drops opencontainers/runc

Kubernetes-commit: 0eaee48ecb8669dc65bfdf9a3583326ab88fc39d
2025-03-08 01:27:22 +00:00
Davanum Srinivas 7802db1890 update to v1.22.0-rc.0
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 97a54dc4b04b7d2938d11c5ae9a6233348e854ef
2025-03-07 13:45:34 -05:00
Kubernetes Publisher a3f7d4eded Merge pull request #130543 from thockin/error_matcher_and_origin
Fix up ErrorMatcher from feedback

Kubernetes-commit: 4696667025efa26a7b192b1cb5cf79cec276f2b4
2025-03-06 00:57:52 -08:00
Davanum Srinivas 4dfd1a614e update to latest cadvisor @ v0.52.0
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 5ecddb65715af7e2afc4f3cbb1abe393bfb4346a
2025-03-04 14:29:08 -05:00
Tim Hockin a70cc77ccb Fix up ErrorMatcher from feedback
a) Rename the type and drop the constructor
b) Make MatchErrors() into a Test() method

For followup:

c) Consider making ByType() assumed
d) Consider making ByField() assumed and handle nil as "don't care"
e) Consider making ByValue() assumed and handle nil as "don't care"

Kubernetes-commit: 0a9f492eedf6dd68fee12e4606d3fef4d608d88f
2025-03-03 10:23:18 -08:00
Kubernetes Publisher b5eba295a2 Merge pull request #130511 from z1cheng/issue_130395
Implement tests for encoding collections in Proto

Kubernetes-commit: c496aef96d8decb22aee5490fb752c029b98a856
2025-03-05 17:28:05 +00:00
Kubernetes Publisher e93b7f2f82 Merge pull request #130549 from jpbetz/validation-gen-pr2
KEP-5073: Add declarative validation to scheme

Kubernetes-commit: 89d0b7022a81dd8b54efcc203b0cfa4502171cae
2025-03-04 14:50:02 -08:00
Joe Betz 39f6713fb4 Add declarative validation to scheme
Kubernetes-commit: 5ff334a1589611c139a03238803ba5abf090eebd
2025-03-03 19:36:50 -05:00
Kubernetes Publisher 9dca0b5e16 Merge pull request #130349 from jpbetz/validation-gen-pr1
KEP-5073: Declarative Validation: Add validation generator

Kubernetes-commit: a5dda5d879cdae6562134ca7881ddf7f672f595d
2025-03-03 11:25:42 -08:00
Joe Betz 14ab9701c6 Add validators: immutable
Co-authored-by: Tim Hockin <thockin@google.com>
Co-authored-by: Aaron Prindle <aprindle@google.com>
Co-authored-by: Yongrui Lin <yongrlin@google.com>

Kubernetes-commit: a2f47e6586f3e7362d7354c35df8e1cfbb86af8f
2025-03-03 09:49:51 -05:00
Joe Betz 93247ca898 Add validators: optional/required/forbidden
Co-authored-by: Tim Hockin <thockin@google.com>
Co-authored-by: Aaron Prindle <aprindle@google.com>
Co-authored-by: Yongrui Lin <yongrlin@google.com>

Kubernetes-commit: 63050550c35b7e2fd2754d7d2b449873feefc203
2025-03-03 09:49:51 -05:00
Joe Betz f6058d5a5b Add validators: eachkey, eachval, subfield
Introduce a composable set of tags for validating child data.
This allows for point-of-use validation of shared types.

Co-authored-by: Tim Hockin <thockin@google.com>
Co-authored-by: Aaron Prindle <aprindle@google.com>
Co-authored-by: Yongrui Lin <yongrlin@google.com>

Kubernetes-commit: 31f463721701a35c1061935d554a81141b0b68bb
2025-03-03 09:49:51 -05:00
Joe Betz 77caaf986c Add validation-gen test infrastructure
Introduces the infrastructure for testing validation-gen tags.

Co-authored-by: Tim Hockin <thockin@google.com>
Co-authored-by: Aaron Prindle <aprindle@google.com>
Co-authored-by: Yongrui Lin <yongrlin@google.com>

Kubernetes-commit: 8c41bdf05b1539bb79eb7bb6f3c5323d526b97d3
2025-03-03 09:49:51 -05:00
Joe Betz 13b1842540 Introduce validation-gen
Adds code-generator/cmd/validation-gen. This provides the machinery
to discover `//+` tags in types.go files, register plugins to handle
the tags, and generate validation code.

Co-authored-by: Tim Hockin <thockin@google.com>
Co-authored-by: Aaron Prindle <aprindle@google.com>
Co-authored-by: Yongrui Lin <yongrlin@google.com>

Kubernetes-commit: c1f9e6b8eed30b6509a8a50f1ae9be1748f07f2e
2025-03-03 09:49:50 -05:00
Kubernetes Publisher 4e966741ac Merge pull request #128919 from dashpole/update_otel
Update go.opentelemetry.io dependencies to v1.33.0/v0.58.0

Kubernetes-commit: eea2f78e61fe91bb8fcd3c4a357ea3a10d1389db
2025-03-02 00:00:37 +00:00
David Ashpole ded50ecb1b update go.opentelemetry.io dependencies to v1.33.0/v0.58.0
Kubernetes-commit: 29c219dcebe30be99d6917623f8d8707a47194c1
2025-03-01 19:17:16 +00:00
z1cheng e8d821eb16 Implement tests for encoding collections in Proto
Signed-off-by: z1cheng <imchench@gmail.com>

Kubernetes-commit: b88f026053a84a135f4bca46ab569d7e3091c2b5
2025-03-01 13:42:32 +08:00
Kubernetes Publisher a3e3122ed9 Merge pull request #130388 from thockin/error_matcher_and_origin
Add an error matcher, convert 2 tests

Kubernetes-commit: aad87f2ee9761326850347f29c034c451117737d
2025-03-01 04:01:10 +00:00
Kubernetes Publisher 7d0dbe29a4 Merge pull request #129334 from serathius/streaming-json-list-encoder
Streaming json list encoder

Kubernetes-commit: 2fc329c857035676492aa6e6a995ef31448465f0
2025-03-01 00:01:16 +00:00
Kubernetes Publisher 609a76591d Merge pull request #130474 from dims/bump-x/crypto-and-x/oauth2
Bump x/oauth2 and x/crypto

Kubernetes-commit: 01ed8ed4ff0a0cbea99370c7a268019829d19e82
2025-02-28 20:01:12 +00:00
Kubernetes Publisher 6c5685cb7e Merge pull request #130355 from yongruilin/validation_origin
validation: Add Origin field to field.Error for more precise error tracking

Kubernetes-commit: 803e9d64952407981b3815b1d749cc96a39ba3c6
2025-02-28 12:01:10 +00:00
Kubernetes Publisher 758f86daa8 Merge pull request #129688 from cpanato/update-main-go124
[go] Bump images, dependencies and versions to go 1.24.0

Kubernetes-commit: b8c95e1954ef222988c0dfe5b45d5cc96c09bcb8
2025-02-27 20:01:16 +00:00
Davanum Srinivas f7c9d8b15c Bump x/oauth2 and x/crypto
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 0fede7b8a2fb4c7f120876c9ef1e826f8ef28da2
2025-02-27 10:59:37 -05:00
Jordan Liggitt 68b2a8167a Switch to private instances of rand for seeding for tests
Kubernetes-commit: 8090db5dcfdd2cae013c69c12ac49b2186d0e383
2025-02-25 09:56:21 -05:00
cpanato aac66c809b bump go.mod to set min go1.24
Signed-off-by: cpanato <ctadeu@gmail.com>

Kubernetes-commit: 88300c406b9199ed017e1bada29951fc18e66ae1
2025-02-25 13:21:52 +01:00
Kubernetes Publisher ee1e055b7c Merge pull request #130220 from serathius/streaming-json-tests
Add tests for encoding collections in JSON for KEP-5116

Kubernetes-commit: dc3021b156a6a5adc4b4898d366822529bcc74a4
2025-02-24 15:44:52 +00:00
Tim Hockin 46d8d84c42 Add an error matcher, convert 2 tests
I fixed up the TestValidateEndpointsCreate path to show the matcher
instead of manual origin checking.

I picked TestValidateTopologySpreadConstraints because it was the last
failing test on my screen when I changed on of the commonly hard-coded
error strings. I fixed exactly those validation errors that were needed
to make this test pass.  Some of the Origin values can be debated.

The `field/testing.Matcher` interface allows tests to configure the
criteria by which they want to match expected and actual errors.  The
hope is that everyone will use Origin for Invalid errors.

There's some collateral impact for tests which use exact-comparisons and
don't expect origins.  These are all candidates for using the matcher.

Kubernetes-commit: c8111709e564d171b23698b3cad0f86e9733ec43
2025-02-23 22:53:29 -08:00
Tim Hockin fa95ab3e7c Fix nits from PR 130355
Kubernetes-commit: 6b7e38f0180b26f875387a935ee814bfd426a70f
2025-02-27 22:43:41 -08:00
Kubernetes Publisher a783532045 Merge pull request #130187 from mansikulkarni96/129084
fix:  Sweep and fix stat, lstat, evalsymlink usage for go1.23 on Windows

Kubernetes-commit: ef54ac803b712137871c1a1f8d635d50e69ffa6c
2025-02-22 23:44:38 +00:00
yongruilin 0571dbf93e test: convert ValidateEndpointsCreate to use error Origin field in test
Update ValidateEndpointsCreate validation tests to use the new Origin field for more precise error comparisons. It leverage the Origin field instead of detailed error messages, improving test robustness and readability.

Co-authored-by: Tim Hockin <thockin@google.com>

Kubernetes-commit: 07477c656e7f3aa8c9d44e769bd2dfbdcc374b85
2025-02-21 22:34:00 +00:00
yongruilin da1b1a9e4c feat: Add Origin field to Error and related methods
This change introducing a new field in Error. It would be used in testing to compare the expected errors without matching the detail strings.

Co-authored-by: Tim Hockin <thockin@google.com>

Kubernetes-commit: 02f7dc55d162fcde7377b1cb0c5e2f9e8974548e
2025-02-21 20:20:47 +00:00
Marek Siarkowicz e38241d3e7 Add tests for encoding collections in JSON for KEP-5116
Used test cases from:
* Original PR https://github.com/kubernetes/kubernetes/pull/129334
* KEP https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5116-streaming-response-encoding#unit-tests

For now testing current serializer implementation to show encoder
behavior and agree on set of tests. Having a separate PR should make review easier.
In separate PR will add the implementation for streaming that should
provide same response byte-to-byte.

Kubernetes-commit: 26fe6bc6e09d81cc6a6253393bc5c2c4bf35c077
2025-02-17 16:50:34 +01:00
Jordan Liggitt 0a4167067c Drop winsymlink go 1.23 workaround
(cherry picked from commit 3990b6324d0427eaf9ff970da2be02711567ef5f)

Kubernetes-commit: 1f642c79c3192994e76bbe8e7360fd661cd21ab4
2025-02-06 12:45:52 -05:00
Kubernetes Publisher 47e7fa9a40 Merge pull request #130151 from marosset/windows-unit-tests-externaljwt-plugin-fixes
fixing k8s.io/kubernetes/pkg/serviceaccount/externaljwt/plugin unit tests on Windows

Kubernetes-commit: 8dbc6739e0729e1a144a72d6280556ff7bfd3a9c
2025-02-14 13:44:20 -08:00
Mark Rossetti 11b535c1d8 fixing various unit tests on Windows that create abstract sockets
by now having them create file-based sockets on windows/darwin

Signed-off-by: Mark Rossetti <marosset@microsoft.com>

Kubernetes-commit: 5e6611af5594078a76694f0cf9fb7eef1299ff40
2025-02-13 13:09:19 -08:00
Kubernetes Publisher 46c230ea8d Merge pull request #130049 from aojea/avoid_ginkgo_dep
reduce dependencies in apimachinery net testing utils

Kubernetes-commit: 670b98bf9229bb1f193571949871f794f7b11ec3
2025-02-11 11:44:40 +00:00
Kubernetes Publisher a19f1f8137 Merge pull request #129792 from likakuli/fix-errshortbuffer
fix: Fix the issue of relist caused by client-side timeout

Kubernetes-commit: 15a186a888dc2e908681c876e321468b8d32a37b
2025-02-10 16:46:08 +00:00
Antonio Ojea d8c2c0ad39 reduce dependencies in apimachinery net testing utils
Consumers of the kubernetes golang API and clients must use
k8s.io/api,apimachinery,client-go. This is also require to download all
the necessary dependencies.

The apimachinery code contains a testing util for proxies that is used
in client-go and in the kubectl e2e. Since the tests on e2e require
ginkgo and we want to ensure this testing library is not used in
production, we cast the interface to match one of those libraries, but
the problem is that this forces consumers of apimachinery to also
download the ginkgo library.

Since NewHTTPProxyHandler receives a testing.TB interface, there is no
need to cast the interface, if someone wants to use it by implementing a
testing interface it is already aware of the risks.

Kubernetes-commit: af3b9e613d3b76b826369153760a069aabb4cf7f
2025-02-08 15:27:20 +00:00
Kubernetes Publisher 12352425f4 Merge pull request #129341 from pohly/log-client-go-watch
client-go watch: context support

Kubernetes-commit: a02fe24385a36f43e96844e19c438cc4097704ef
2025-02-07 12:46:01 +00:00
Kubernetes Publisher a2cb7d3ca7 Merge pull request #125046 from tklauser/min-max-builtins
Use Go 1.21 min/max builtins

Kubernetes-commit: 586f0fad5cc25d7b40ec5ee1dbfa8249b05f1a19
2025-01-30 16:17:31 +00:00
Kubernetes Publisher 4c615911e2 Merge pull request #129815 from dims/linter-to-ensure-go-cmp/cmp-is-used-only-in-tests
Linter to ensure go-cmp/cmp is used ONLY in tests

Kubernetes-commit: d36322f8d76c8e2a456e381bcc6bb43e4bbe602c
2025-01-25 15:53:21 -08:00
Davanum Srinivas d5dedd03f2 Linter to ensure go-cmp/cmp is used ONLY in tests
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 4e05bc20db99ff89b2d2205218d24b9935a7fdd7
2025-01-24 17:03:29 -05:00
likakuli f7fd8b3a38 fix: Fix the issue of relist caused by client-side timeout
Signed-off-by: likakuli <1154584512@qq.com>

Kubernetes-commit: 38a21e06f5d72934a63b0c91a87bd75054c01172
2025-01-24 11:44:32 +08:00
Kubernetes Publisher 45d29dc4d6 Merge pull request #129611 from carlory/cleanup-WatchBookmark
remove WatchBookmark feature-gate comment from types.go

Kubernetes-commit: 4aff9774bb6e87913e80071c6154850a2b665a8e
2025-01-17 04:16:10 +00:00
Kubernetes Publisher c74304d2a6 Merge pull request #129346 from pohly/log-client-go-apimachinery-wait
apimachinery wait: support contextual logging

Kubernetes-commit: 2a425157ba4ef225d7dab4b85ef649d820cbe8cf
2025-01-16 20:16:10 +00:00
Kubernetes Publisher f863467e6f Merge pull request #129633 from skitt/revert-go-difflib-go-spew
Revert to go-difflib and go-spew releases

Kubernetes-commit: 6d570c923f66a1f214d0c3ba3eddd9a0cd0fae68
2025-01-15 20:16:02 +00:00
Stephen Kitt 808ab771db Revert to go-difflib and go-spew releases
The last dependency pulling in the tips of go-difflib and go-spew has
reverted to the last release of both projects, so k/k can revert to
the releases too. As can be seen from the contents of vendor, this
doesn't result in any actual change in the code.

Signed-off-by: Stephen Kitt <skitt@redhat.com>

Kubernetes-commit: 3986472b3c7202716f92e586ccfaa4b4fe573dc5
2025-01-15 09:07:27 +01:00
carlory 5036924849 remove WatchBookmark feature-gate comment from types.go
Kubernetes-commit: bc488020870b748d39d24c125b103e8891f00a3c
2025-01-14 18:15:13 +08:00
Kubernetes Publisher 3e8e52d6a1 Merge pull request #129349 from dims/bump-x/net-to-v0.33.0
Bump x/net to v0.33.0

Kubernetes-commit: b7ef173c59065f9a5f68eb514ef0483c6f3887ae
2025-01-06 20:15:45 +00:00
Davanum Srinivas c485e50d18 Bump x/net to v0.33.0
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 0b6e3718340fa7e3846cf9b7d5a0f7a684a6fa5a
2024-12-20 14:30:57 -05:00
Patrick Ohly 675c4f7a80 client-go + apimachinery watch: context support
The Lister and Watcher interfaces only supported methods without context, but
were typically implemented with client-go API calls which need a context. New
interfaces get added using the same approach as in
https://github.com/kubernetes/kubernetes/pull/129109.

Kubernetes-commit: 6688adae142e37114d9dfa8d94cd1d8a91fbcc13
2024-12-20 13:55:47 +01:00
Marek Siarkowicz 1e89b89bc5 Streaming JSON encoder for List
Kubernetes-commit: e7c743b2ebfaed1e3132027c0369ac25b14b6f47
2024-12-19 10:38:30 +01:00
Kubernetes Publisher 307a3ddd3c Merge pull request #126387 from pohly/log-client-go-tools-cache-apis
client-go/tools/cache: add APIs with context parameter

Kubernetes-commit: e305c3398896b04c0d3b58d64531f8a87f685f68
2024-12-18 21:44:40 +00:00
Kubernetes Publisher a1a247aa25 Merge pull request #129257 from liggitt/coerce-labels-annotations
Coerce null label and annotation values to empty string

Kubernetes-commit: 2a609cd6e26162fd44c5660bfdf206168efdd60c
2024-12-18 01:54:52 +01:00
Jordan Liggitt 8d8a7acdf9 Coerce null label and annotation values to empty string
Kubernetes-commit: 13b84453df54f8cc876134d65ad870496bd06251
2024-12-17 11:02:03 -05:00
Kubernetes Publisher 767f17a6af Merge pull request #129213 from Jefftree/k-openapi
Bump kube-openapi

Kubernetes-commit: 13eb074ddd231d127709f0410185eeca68a69c8a
2024-12-14 01:44:15 +00:00
Jefftree ff1373e940 bump kube-openapi
Kubernetes-commit: 3269f4bb94c58dfe577621c42f88ea06fbdd79a7
2024-12-13 20:50:49 +00:00
Kubernetes Publisher cfa44a126e Merge pull request #128659 from saschagrunert/google-go-protobuf
Replace `github.com/golang/protobuf` with `google.golang.org/protobuf`

Kubernetes-commit: b1f2af04328936c2fa79db4af14f5c6ad9160748
2024-12-13 17:44:34 +00:00
Patrick Ohly b6d30bdf92 apimachinery wait: support contextual logging
By passing the context, if available, down into the actual wait loop it becomes
possible to use contextual logging when handling a crash. That then logs more
information about which component encountered the problem (WithName e.g. in
kube-controller-manager) and what it was doing at the time (WithValues e.g. in
kube-scheduler).

Kubernetes-commit: e681a790584ada4c0a7813f32698fc3ff0784305
2024-12-11 12:57:40 +01:00
Patrick Ohly 6091c6aa75 k8s.io/apimachinery: add HandleCrashWithLogger and HandleErrorWithLogger
There are situations where it makes more sense to pass a logger through a
call chain, primarily because passing a context would imply that the call chain
should honor cancellation even though there is a different shutdown mechanism.

Using the *WithContext variants would cause additional overhead for
klog.NewContext, which hurts in particular for HandleCrash because that
function is typically a nop that doesn't actually need to log
anything. HandleCrashWithLogger avoids that overhead.

For HandleError that is less relevant because it always logs, but for the sake
of symmetry it also gets added.

Putting klog.Logger (= logr.Logger) into the public Kubernetes Go API is okay
because it's no longer realistic that these packages can ever drop the klog
dependency. Callers using slog as logger in their binary can use
https://github.com/veqryn/slog-context to store a slog.Logger in a context and
then call the *WithContext variants, klog.FromContext will be able to use it.
This is probably very rare, so there's no need for *WithSlog variants.

While at it, unit testing gets enhanced and logging in panic handlers gets
improved such that they are guaranteed to get a saner location when not
doing any caller skipping. Previously, this was undefined.

Kubernetes-commit: e3c584030c3700bababcfcd63dfe28c2d1c420e2
2024-11-28 15:19:58 +01:00
Sascha Grunert 5765d816ab Replace `github.com/golang/protobuf` with `google.golang.org/protobuf`
Signed-off-by: Sascha Grunert <sgrunert@redhat.com>

Kubernetes-commit: c1d0e870f4f45548c0d5b2bf83f36fb208252978
2024-11-07 12:47:08 +01:00
Kubernetes Publisher 7249ce11e4 Merge pull request #129195 from dims/update-x/crypto/ssh-dependency
Update x/crypto/ssh dependency to v0.31.0

Kubernetes-commit: b21ab179c74a270cd276d2dbb5f4b55730838096
2024-12-13 09:44:12 +00:00
Davanum Srinivas f228881001 Update x/crypto/ssh dependency
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 80735180ab2c61232dcc4646e693ddcaeaf96ca3
2024-12-12 20:46:15 -05:00
Kubernetes Publisher d2cfef5abd Merge pull request #129170 from benluddy/cyclic-marshaler-cache-race
Fix data race in CBOR serializer's custom marshaler type cache.

Kubernetes-commit: 5c207d6fb028ce2f79373626c73dd8c60d0b57ae
2024-12-12 17:44:02 +00:00
Kubernetes Publisher 146e532b55 Merge pull request #129054 from pohly/remove-import-name
remove import doc comments

Kubernetes-commit: e8615e27125518f0ed0ba06244b7ecee21451bb0
2024-12-12 09:44:00 +00:00
Kubernetes Publisher 6e120e1a46 Merge pull request #127897 from modulitos/add-x509-uid-to-user
Set User.UID from x509 cert

Kubernetes-commit: ed8999ed64d4f6e05859f83456f279949bac7907
2024-12-12 05:44:20 +00:00
Ben Luddy 373952f922 Fix data race in CBOR serializer's custom marshaler type cache.
A placeholder entry is added to the cache while the permanent entry is being constructed. A data
race existed where the placeholder entry itself could be mutated after its address may have been
given to other callers.

Kubernetes-commit: c9066d75f6d23384df20be23557a26a4b9aac871
2024-12-11 18:38:09 -05:00
Kubernetes Publisher 8c60292e48 Merge pull request #129103 from liggitt/drop-winreadlinkvolume
Drop use of winreadlinkvolume godebug option

Kubernetes-commit: bfe431b53e600c9a36c46eef0f6ecfcf37265d60
2024-12-06 18:16:43 +00:00
Jordan Liggitt f9c043ade2 Drop use of winreadlinkvolume godebug option
Kubernetes-commit: 3046fe23d4fe4ba86713ffd61bf0e07156b2b7c3
2024-12-06 02:40:53 -05:00
Kubernetes Publisher a0ca8148bd Merge pull request #129083 from liggitt/go1.23windows
Revert to go1.22 windows filesystem stdlib behavior

Kubernetes-commit: 6fc64a261c1dca857a5a7fd1bc87fae38dbe1c8a
2024-12-04 22:16:43 +00:00
Jordan Liggitt 65d79dc805 Revert to go1.22 windows filesystem stdlib behavior
Kubernetes-commit: 3878a3a6de64660e356a35f70471c27a09698090
2024-12-04 09:52:56 -05:00
Patrick Ohly 15f29b93dc remove import doc comments
The "// import <path>" comment has been superseded by Go modules.
We don't have to remove them, but doing so has some advantages:

- They are used inconsistently, which is confusing.
- We can then also remove the (currently broken) hack/update-vanity-imports.sh.
- Last but not least, it would be a first step towards avoiding the k8s.io domain.

This commit was generated with
   sed -i -e 's;^package \(.*\) // import.*;package \1;' $(git grep -l '^package.*// import' | grep -v 'vendor/')

Everything was included, except for
   package labels // import k8s.io/kubernetes/pkg/util/labels
because that package is marked as "read-only".

Kubernetes-commit: 8a908e0c0bd96a3455edf7e3b5f5af90564e65b0
2024-12-02 14:43:58 +01:00
Kubernetes Publisher 96b97de8d6 Merge pull request #127513 from tkashem/delete-undecryptable
KEP-3926: unsafe deletion of corrupt objects

Kubernetes-commit: 4d10ae8fdc579e2bb09789507cae7b8d32cbd306
2024-11-08 02:21:04 +00:00
modulitos aa837c28fd set user.DefaultInfo.UID from x509 cert
Kubernetes-commit: b577972a551ea0dbc22f29ac97f0a0e621d42e1b
2024-10-06 19:28:21 -07:00
Abu Kashem 16af2ff33f implement unsafe deletion, and wire it
- implement unsafe deletion, and wire it
- aggregate corrupt object error(s) from the storage LIST operation
- extend storage error:
a) add a new type ErrCodeCorruptObj to represent a corrupt object:
b) add a new member 'InnerErr error' to StorageError to hold
   the inner error
- add API status error

Kubernetes-commit: 5d4b4a160dc551dc8979012eeabea1a098945603
2024-09-20 17:36:27 -04:00
Abu Kashem 6ff8305a00 api: run codegen
run 'make update' to code gen for changes in meta/v1 DeleteOptions

Kubernetes-commit: aff05b0bcad2b62730e101067a04ffc75a96a41b
2024-09-23 13:29:15 -04:00
Abu Kashem ca9b8b2341 api: add a new field to meta/v1 DeleteOptions
- add a new boolean field
  IgnoreStoreReadErrorWithClusterBreakingPotential to meta/v1 DeleteOptions

- add validation for the new delete option
add validation for the new field in the delete options
ignoreStoreReadErrorWithClusterBreakingPotential

- prevent the pod eviction handler from issuing an unsafe pod delete
prevent the pod eviction handler from enabling the
'ignoreStoreReadErrorWithClusterBreakingPotential' delete option

Kubernetes-commit: b6773f15897dc31190b2be7cb49dd02015440465
2024-09-23 12:22:53 -04:00
Kubernetes Publisher d941d9fb4c Merge pull request #128503 from benluddy/cbor-codecs-featuregate
KEP-4222: Wire serving codecs to CBOR feature gate.

Kubernetes-commit: 6399c32669c62cfbf7c33b14b77d6781ce1cce27
2024-11-06 23:17:35 +00:00
Ben Luddy 3b4250faac Wire serving codecs to CBOR feature gate.
Integration testing has to this point relied on patching serving codecs for built-in APIs. The
test-only patching is removed and replaced by feature gated checks at runtime.

Kubernetes-commit: 439d2f7b4028638b3d8d9261bb046c3ba8d9bfcb
2024-11-01 16:05:32 -04:00
Kubernetes Publisher daaad096b3 Merge pull request #128501 from benluddy/watch-cbor-seq
KEP-4222: Use cbor-seq content-type for CBOR watch responses.

Kubernetes-commit: a885e446d6f6f5530da4923a3872eb27ca47bdc0
2024-11-06 22:59:11 +00:00
Kubernetes Publisher d70754f32a Merge pull request #128380 from pohly/log-TODO-logcheck
apiserver: avoid TODO in public docs

Kubernetes-commit: 432a9af0fbcea0fec3eed631f628c8da71a32c45
2024-11-06 22:59:10 +00:00
Kubernetes Publisher a4471387da Merge pull request #128553 from thockin/master
Validation: merge TooLong and TooLongMaxLen

Kubernetes-commit: a50b4e52a96b05e14cdb119be9947c346ed0f2cd
2024-11-06 06:59:03 +00:00
Kubernetes Publisher b5e810677b Merge pull request #128580 from jpbetz/bump-kube-openapi
Bump kube-openapi to latest

Kubernetes-commit: 9a2a7537f035969a68e432b4cc276dbce8ce1735
2024-11-05 22:59:05 +00:00
Kubernetes Publisher fab67710c0 Merge pull request #128462 from benluddy/cbor-direct-custom-marshalers
KEP-4222: Reject custom marshalers from direct CBOR Marshal and Unmarshal.

Kubernetes-commit: f0ff87087101b0d45007f770db691b053305196a
2024-11-05 22:59:03 +00:00
Tim Hockin 6a84120b17 Call-site comments: the "" arg to TooLong is unused
Kubernetes-commit: c8eeb486f480f167a6b6ee4c162dbb1d84a4f4a9
2024-11-05 09:11:03 -08:00
Joe Betz dbd5f43ac2 hack/pin-dependency.sh k8s.io/kube-openapi 32ad38e42d3faf1ce94eb29f4ea6d763339b258e
Kubernetes-commit: f2157ff73e3c9b7c2a36bf371e388e8976d93975
2024-11-05 10:18:57 -05:00
Tim Hockin 44ca070f44 Clarify that value arg to field.TooLong is unused
Kubernetes-commit: 8a7af903003c1347af6643d9218a933512ce2785
2024-11-04 15:44:40 -08:00
Tim Hockin 51cdbebdbe Kill TooLongMaxLength() in favor of TooLong()
Kubernetes-commit: 4d0e1c8fd4c6577d90dfa1fca67113b8b0af739a
2024-11-04 15:16:29 -08:00
Kubernetes Publisher 9500acc2d9 Merge pull request #128507 from dims/use-k8s.io/utils/lru-instead-of-github.com/golang/groupcache/lru
Use k8s.io/utils/lru instead of github.com/golang/groupcache/lru

Kubernetes-commit: 7a4d755644e83dfade7bbc4c240c204a9e54d9c0
2024-11-04 22:59:08 +00:00
Davanum Srinivas cc3132f4a0 Use k8s.io/utils/lru instead of github.com/golang/groupcache/lru
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 2b0592ee77d0a0bb3017df042066ecb8c83d2fb3
2024-11-01 22:19:11 -04:00
Kubernetes Publisher f6d2366c38 Merge pull request #128481 from carlory/dependencies-ginkgo-gomega
dependencies: ginkgo v2.21.0, gomega v1.35.1

Kubernetes-commit: 88a997ac01cd8a386df9318d81b89257307f53fb
2024-11-01 18:58:53 +00:00
Ben Luddy 8019856689 Use application/cbor-seq media type in streaming CBOR responses.
The media type application/cbor describes exactly one encoded item. As a new (to Kubernetes) format
with no existing clients, streaming/watch responses will use the application/cbor-seq media
type. CBOR watch responses conform to the specification of CBOR Sequences and are encoded as the
concatenation of zero or more items with no additional framing.

Kubernetes-commit: 504f14998e920ca8837b3310094b3da11c62a070
2024-11-01 13:14:06 -04:00
carlory 27a0d277f3 dependencies: ginkgo v2.21.0, gomega v1.35.1
Kubernetes-commit: 80b1a297865500891bd823005ace761becae5dbf
2024-11-01 11:35:24 +08:00
Kubernetes Publisher cbe039cffd Merge pull request #128416 from jpbetz/reset-filter
Add optional ResetFieldsFilterStrategy interface for storage

Kubernetes-commit: b831df733e5cf244331f61fffb0ba86787b27236
2024-11-01 02:58:44 +00:00
Ben Luddy d695394281 Reject custom marshalers from direct CBOR Marshal and Unmarshal.
Types that implement any of the stdlib text and JSON marshaler and unmarshaler interfaces without
implementing the corresponding CBOR interfaces are currently rejected by the CBOR serializer. This
is a temporary measure for the initial alpha; such types will ultimately be handled via automatic
transcoding. The "cbor/direct" subpackage exports Marshal and Unmarshal functions to support the
implementation of custom CBOR marshalling and unmarshalling behaviors, but did not include the
safeguard against handling non-CBOR custom marshalers.

Kubernetes-commit: 0b7b42cc1010abcec0eaf6d0f6ffe40084fd0df7
2024-10-30 16:32:11 -04:00
Joe Betz 53a03793a9 Add ResetFieldsFilterStrategy
Kubernetes-commit: 2bc17d1cf03f2f2bcd683e7e79f01c929951cca3
2024-10-29 12:03:32 -04:00
Joe Betz b7e0b1686d hack/pin-dependency.sh sigs.k8s.io/structured-merge-diff/v4 v4.4.2
Kubernetes-commit: 6fe51403665f1b6e820226004817b92e3118cabc
2024-10-31 21:19:15 -04:00
Kubernetes Publisher a25029a669 Merge pull request #128456 from benluddy/nondeterministic-response-encoding
KEP-4222: Allow nondeterministic object encoding in HTTP response bodies.

Kubernetes-commit: dc1d7f41ef4765552193c10cf1c1ed2b0c4e149b
2024-10-30 22:43:44 +00:00
Kubernetes Publisher c56b2072ee Merge pull request #128273 from benluddy/cbor-apply
KEP-4222: Support CBOR encoding for apply requests.

Kubernetes-commit: 16f9fdc7057e1f69ff1a44e3dbbcf7b994c3cd29
2024-10-30 17:25:25 +00:00
Ben Luddy 42acd351a2 Allow nondeterministic object encoding in HTTP response bodies.
Kubernetes-commit: dee76a460ec80f15dc199c93e506586687d42291
2024-10-28 12:09:02 -04:00
Patrick Ohly 230b2165ea apiserver: avoid TODO in public docs
As pointed out in
https://github.com/kubernetes/kubernetes/pull/126387#discussion_r1811009830,
having TODOs in the Go doc comments makes them visible to downstream
developers.

Using "Contextual logging: " as special prefix serves the same
purpose (search/replace as explained in
https://github.com/kubernetes/kubernetes/issues/126379) and makes the doc
comment human-readable. Keeping that information visible is useful because
developers might care, even if logcheck doesn't warn them yet.

Kubernetes-commit: fc6e0a5799842ccb505521e57e718dcc003a6674
2024-10-28 09:01:51 +01:00
Ben Luddy 769bb80d62 Support application/apply-patch+cbor in patch requests.
Kubernetes-commit: 37ed906a33211c7d578cab2d681941ebfd2f2f23
2024-10-22 16:08:24 -04:00
Kubernetes Publisher 124c262107 Merge pull request #128243 from benluddy/cbor-dynamic-integration
KEP-4222: Add CBOR variant of admission webhook integration test.

Kubernetes-commit: 5147eebf224ae41892b736179ca91c47fd794565
2024-10-25 01:04:53 +01:00
Ben Luddy 74ceb68114 Export meta internal version scheme for testing.
Codecs is already exported, but in order for tests to construct an alternate CodecFactory for meta's
internal version types, they either need to be able to reference the scheme or to construct a
parallel scheme, and a parallel scheme construction risks going out of sync with the way the
package-scoped scheme object is initialized.

Kubernetes-commit: 3e1b6aaf41f2e05146e313d47ec9c288bc554a56
2024-10-17 08:53:45 -04:00
Ben Luddy 840a63c089 Add CBOR serializer option to disable JSON transcoding of raw types.
Kubernetes-commit: d638d64572e1e0086c4e762aee95c6e7a5db1a9e
2024-10-16 17:42:06 -04:00
Ben Luddy 02c4999eae Add WithSerializer option to add serializers to CodecFactory.
Kubernetes-commit: db1239d354f90454a158ff8d0a27e6b8d6e927b0
2024-10-16 17:41:00 -04:00
Ben Luddy 009e86342d Use runtime.SerializerInfo in place of internal "serializerType".
CodecFactory construction uses an unexported struct type named "serializerType" to hold serializer
definitions. There are few differences between it and runtime.SerializerInfo, and they do not appear
to be used anymore. For example, serializerType includes an unused FileExtensions field, and has
distinct ContentType (singular) and AcceptContentTypes (plural) fields instead of
runtime.SerializeInfo's singular MediaType. All remaining uses of serializerType set
AcceptContentTypes to a single-entry slice whose element is equal to its ContentType field.

During construction of a CodecFactory, all serializerType values were already being mechanically
translated into runtime.SerializerInfo values.

Moving to an exported type for serializer definitions makes it easier to expose an option to allow
callers to register their own serializer definitions, which in turn makes it possible to
conditionally include new serializers at runtime (especially behind feature gates).

Kubernetes-commit: 66a14268c591a30090de4f89185b8d880ddbdf18
2024-10-16 17:27:56 -04:00
Kubernetes Publisher cfee475807 Merge pull request #128165 from liggitt/prune-self-require
Drop self-referencing replace directives

Kubernetes-commit: a8fc7ae761c19ab436cf513c9eed877f08961cf7
2024-10-18 04:22:25 +00:00
Jordan Liggitt ba08122e4c Drop self-referencing replace directives
Kubernetes-commit: 3be1109829d4b0921972bb8b5f66a4d179ff6255
2024-10-17 15:51:15 -04:00
Kubernetes Publisher 51544a5484 Merge pull request #127862 from dinhxuanvu/cbor-fuzz
KEP-4222: Add fuzz test for roundtrip unstructured objects to JSON/CBOR

Kubernetes-commit: e673417529d9d2d2dd04f28f8437cbe1c403dfbe
2024-10-16 16:22:13 +00:00
Kubernetes Publisher 8a237eeb80 Merge pull request #128099 from benluddy/nested-number-as-float64-portability
Make NestedNumberAsFloat64 accuracy test architecture-neutral.

Kubernetes-commit: db0af9bd94efc25693545a69f273a4d26bf6483b
2024-10-15 21:47:03 +01:00
Ben Luddy 0612f08485 Make NestedNumberAsFloat64 accuracy test architecture-neutral.
NestedNumberAsFloat64 will convert int64s to float64 only if the int64 value can be represented
exactly by a float64. The original test for this property used a roundtrip conversion from int64 to
float64 and back, and the behavior of these conversions is inconsistent across architectures.

Kubernetes-commit: e413e026a3949bdf1a83c762bb6d9275aed61f57
2024-10-15 13:10:19 -04:00
Kubernetes Publisher 2b29434a8c Merge pull request #125570 from sanchezl/test-additional-types
KEP-4222: Cover aggregator and apiextension types in unstructured roundtrip test.

Kubernetes-commit: 4812ea8aa5a49384ca8db7770da299fea6a48015
2024-10-12 05:48:09 +00:00
Kubernetes Publisher ee6d9667b4 Merge pull request #127998 from skitt/golang-x-oct-2024
October 2024 golang.org/x bump

Kubernetes-commit: 8cbb11519c54c120e2dc120a4799e53abbfea4a4
2024-10-11 13:48:55 +00:00
Kubernetes Publisher 2561a8113c Merge pull request #127985 from dims/update-moby-runc-dependencies-oct-10
Update moby/runc dependencies

Kubernetes-commit: 6e5e8f374e834fa8dab341bde5c522704ed55ba6
2024-10-11 13:48:52 +00:00
Stephen Kitt 68b71855f1 October 2024 golang.org/x bump
Nothing major here, but nothing liable to cause pain to downstreams
either.

* https://github.com/golang/crypto/compare/v0.26.0...v0.28.0 (there’s
  a SHA3 fix there but it’s only relevant for 32-bit platforms)
* https://github.com/golang/net/compare/v0.28.0...v0.30.0 (mostly
  http2; route address parsing fix on Darwin)
* https://github.com/golang/oauth2/compare/v0.21.0...v0.23.0 (Google
  license fix)
* https://github.com/golang/sys/compare/v0.23.0...v0.26.0 (faster
  getrandom() on Linux through the vDSO; improved RISC-V support)
* https://github.com/golang/term/compare/v0.23.0...v0.25.0
* https://github.com/golang/time/compare/v0.3.0...v0.7.0 (0-limit
  handling fix in x/time/rate; Google license fix)
* https://github.com/golang/tools/compare/v0.24.0...v0.26.0

This doesn’t include golang.org/x/exp; that doesn’t have any relevant
changes. There’s an apidiff fix but we always pull in the latest
apidiff anyway.

Signed-off-by: Stephen Kitt <skitt@redhat.com>

Kubernetes-commit: 6c5a528727c30803d4426b29c06ae5d350619877
2024-10-11 10:22:13 +02:00
Davanum Srinivas ebca14730a Update moby/runc dependencies
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 521f2d106b9c5744ce57a8ec03124bcdadbca986
2024-10-10 11:58:22 -04:00
Kubernetes Publisher ea28d546a9 Merge pull request #127942 from liggitt/json123
sigs.k8s.io/json go 1.23 bump

Kubernetes-commit: fc318e3ba4cb1db822d7f802993059846f64179d
2024-10-10 21:48:51 +00:00
Jordan Liggitt 9f62123d8e Update sigs.k8s.io/json to go1.23
Kubernetes-commit: 8eff759b6ac7c3bb0c6a8823c751f5a578d6f721
2024-10-10 11:00:11 -04:00
Kubernetes Publisher acfda13730 Merge pull request #127838 from benluddy/unstructured-nested-number-as-float64
Add NestedNumberAsFloat64 unstructured field accessor.

Kubernetes-commit: aa09157014750871407aca4217f4ecf07bc64b7c
2024-10-08 17:48:42 +00:00
Kubernetes Publisher c463db1965 Merge pull request #127822 from benluddy/json-serializer-roundtrip-float64-no-fraction
Test concrete type changes in unstructured float64 JSON roundtrips.

Kubernetes-commit: b8e57a1c201914e8c5164a165f446480c7ff0bba
2024-10-04 12:50:29 +01:00
Ben Luddy f0c9bac49f Add NestedNumberAsFloat64 unstructured field accessor.
Go float64 values that have no fractional component and can be accurately represented as an int64,
when present in an unstructured object and roundtripped through JSON, appear in the resulting object
with the concrete type int64. For code that processes unstructured objects and expects to find
float64 values, this is a surprising edge case. NestedNumberAsFloat64 behaves the same as
NestedFloat64 when accessing a float64 value, but will additionally convert to float64 and return an
int64 value at the requested path. Errors are returned on encountering an int64 that cannot be
precisely represented as a float64.

Kubernetes-commit: 30c35a5618c0e3210f855501aab6c8369ee477b5
2024-10-03 13:19:31 -04:00
Ben Luddy 999ccb8311 Test concrete type changes in unstructured float64 JSON roundtrips.
During JSON roundtrips of unstructured objects, float64 values with no fractional component will in
certain cases roundtrip to int64. This is potentially surprising existing behavior. Adding dedicated
test coverage for this will help codify the behavior and mitigate future regression risk.

Kubernetes-commit: 707ee63456c60a4b37275018ad6a044e3700e1c7
2024-10-02 16:38:54 -04:00
Kubernetes Publisher 0db5dbf030 Merge pull request #127691 from mmorel-35/testifylint/expected-actual@k8s.io/apimachinery
fix: enable expected-actual rule from testifylint in module `k8s.io/apimachinery`

Kubernetes-commit: a35cdc2fd96a31364d54c6df961b581ac48276bc
2024-09-29 04:58:08 +01:00
Matthieu MOREL 624012659d fix: enable expected-actual rule from testifylint in module `k8s.io/apimachinery`
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>

Kubernetes-commit: 0006a3cc3753bd64daa49d81cac77763422a90d3
2024-09-27 07:49:18 +02:00
Kubernetes Publisher dc03077c03 Merge pull request #125678 from benluddy/cbor-nondeterministic-encode
KEP-4222:  Support nondeterministic encode for the CBOR serializer.

Kubernetes-commit: a73a27771566ce19d20854f3e963628eddb066d3
2024-09-26 04:17:05 +00:00
Kubernetes Publisher 7f7bf11089 Merge pull request #127319 from p0lyn0mial/upstream-define-initial-events-list-blueprint
apimachinery/meta/types.go: define InitialEventsListBlueprintAnnotationKey const

Kubernetes-commit: 2e6216170b8241076eabdef632f04b388130d280
2024-09-25 04:17:17 +00:00
Vu Dinh d14316707e Add fuzz test for roundtrip unstructured objects to JSON/CBOR
Fuzz: Roundtrip JSON-to-CBOR-to-JSON and CBOR-to-JSON-to-CBOR

Signed-off-by: Vu Dinh <vudinh@outlook.com>

Kubernetes-commit: c96cd52b6a962f0e1d1d8d6a1144dcc415d861a5
2024-09-24 10:12:37 -04:00
Kubernetes Publisher c98a9e2222 Merge pull request #126760 from ncdc/ncdc/emeritus
Move ncdc to emeritus

Kubernetes-commit: f9a57ba82d05a8b9f442092f0b391ae8bd74287c
2024-09-20 20:17:01 +00:00
Kubernetes Publisher f7615f37d7 Merge pull request #127366 from thockin/pr-119910-plus-thockin
Improve precision of Quantity export as float64

Kubernetes-commit: 8adc35ebd7221683b9f9ec7a9e2c63cf3f6d0dff
2024-09-19 08:14:06 +00:00
Kubernetes Publisher c7c91edae8 Merge pull request #127435 from sttts/sttts-getmanagedfields-nocopy
unstructured: avoid deepcopying managedFields

Kubernetes-commit: 30226e6c921c79183cf89c252a10c829bf337775
2024-09-18 16:02:52 +01:00
Dr. Stefan Schimanski e60d3a57ff unstructured: avoid deepcopying managedFields
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@gmail.com>

Kubernetes-commit: 9705cbe2ae4a8c5ab9ee2937e9b55315290d1b2e
2024-09-18 12:55:52 +02:00
Tim Hockin d1fb2f42bc Make accurate Quantity->float64 be a new method
This leaves the old method alone, since the performance difference is so
stark.

```
$ go test ./staging/src/k8s.io/apimachinery/pkg/api/resource/ -bench=Float64
goos: linux
goarch: amd64
pkg: k8s.io/apimachinery/pkg/api/resource
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
BenchmarkQuantityAsApproximateFloat64-6   	95865672	        11.44 ns/op
BenchmarkQuantityAsFloat64Slow-6          	 2800825	       430.2 ns/op
PASS
ok  	k8s.io/apimachinery/pkg/api/resource	2.786s
```

Kubernetes-commit: 595dc5155bed2253d1578d48bc5b4a4184ef9434
2024-09-14 15:21:58 -07:00
Kubernetes Publisher 0fc0110cc2 Merge pull request #125186 from liyuerich/newserializer
drop deprecated json/yaml newSerializers, use json.NewSerializerWithO…

Kubernetes-commit: e5dd48efd07e8a052604b3073e0fafe7361ca689
2024-09-13 15:28:23 +00:00
Lukasz Szaszkiewicz 79b66fa231 apimachinery/meta/types.go: define InitialEventsListBlueprintAnnotationKey const
Kubernetes-commit: 574460e21a9de05c487b1498dc1abbebf97b92eb
2024-09-12 13:04:27 +02:00
Adrian Moisey b78556fffe KEP-4427 : AllowRelaxedDNSSearchValidation (#127167)
* KEP-4427 : AllowRelaxedDNSSearchValidation

* Add e2e test with feature gate to test KEP-4427 RelaxedDNSSearchValidation

* Add more validatePodDNSConfig test cases

Also update Regex to match the case we want.

Thanks Tim and Antonio!

Kubernetes-commit: 8e3adc4df64d5b382c8916610313ce25e0df8e28
2024-09-12 10:41:19 +02:00
Kubernetes Publisher 4e174c5e36 Merge pull request #127271 from liggitt/go1.23
Update go.mod for go 1.23

Kubernetes-commit: c775fb2238e1ed48f62f02898bbb3ecee993e044
2024-09-12 03:28:24 +00:00
Jordan Liggitt 8e938ee141 Pin godebug default to go1.23
Kubernetes-commit: 102a9dbab1764e8793d0237b25143fa49cd96831
2024-09-10 12:22:40 -04:00
Jordan Liggitt f9de91442e Update go.mod to go 1.23
Kubernetes-commit: 65ef53139012dee36c08f558604dea48af170e11
2024-09-10 12:07:06 -04:00
Kubernetes Publisher 2465dc5239 Merge pull request #126787 from Jefftree/update-kube-openapi
Bump k8s.io/kube-openapi and k8s.io/gengo

Kubernetes-commit: f1a922c8e6f951381450ee3c2922ca018f14a82e
2024-08-27 23:27:41 +00:00
Jefftree fbd266437b re-vendor k8s.io/kube-openapi
Kubernetes-commit: ea2bdb6334ec1a2821a96163d83480d5fdb1861b
2024-08-27 01:58:39 +00:00
Andy Goldstein d9292df3ab Use emeritus_*
Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>

Kubernetes-commit: 0e228be96f352359847d013ff889522524e1cb98
2024-08-22 17:48:27 -04:00
Andy Goldstein eec42932ea Move ncdc to emeritus
I am moving myself to emeritus as I am now firmly on the end-user side
of things.

Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>

Kubernetes-commit: 3ab816dcabf37acda33c665ab5aa85f1f6163bc1
2024-08-17 13:00:07 -04:00
Kubernetes Publisher a8a2284d31 Merge pull request #126715 from tklauser/use-go-stdlib-slices
Use Go standard library slices package instead of k8s.io/utils/strings/slices

Kubernetes-commit: 1dd3c64502dfd07137f311fed63408af9c0c2513
2024-08-15 18:03:51 -07:00
Tobias Klauser 7e3bacd244 Use Go standard library slices package instead of k8s.io/utils/strings/slices
The package was introduced in Go 1.21 and is already in use in the k8s
code base.

Kubernetes-commit: 23dcd2604883c7ba91d72f5a9d84706650260c55
2024-08-15 14:43:53 +02:00
Tobias Klauser b93cf3280f Use Go 1.21 min/max builtins
The `min` and `max` builtins are available since Go 1.21[^1]. The
top-level go.mod file is specifying Go 1.22, so the builtins can be
used.

[^1]: https://go.dev/doc/go1.21#language

Kubernetes-commit: b01b016668a3cb687eabbc815725176519f1a098
2024-08-07 10:40:26 +02:00
Kubernetes Publisher 95b78024e3 Merge pull request #126231 from seans3/websocket-https-proxy-fix
Falls back to SPDY for gorilla/websocket https proxy error

Kubernetes-commit: 90a84704d6e103c0a7b5cdaa8f6626a134079147
2024-07-20 13:23:16 -07:00
Sean Sullivan a8f449e276 Falls back to SPDY for gorilla/websocket https proxy error
Kubernetes-commit: 9d560540c5268e0e2aebf5306907494cf522c260
2024-07-19 12:04:41 -07:00
Kubernetes Publisher 62791ecbc5 Merge pull request #125571 from liggitt/filter-auth-02-sar
add field and label selectors to authorization

Kubernetes-commit: 64ba17c605a41700f7f4c4e27dca3684b593b2b9
2024-07-19 15:30:01 -07:00
Luis Sanchez 06007813d7 cover additional types in unstructured roundtrip test
Co-authored-by: Ben Luddy <bluddy@redhat.com>

Kubernetes-commit: aaa7364f608ef302ee5b276e1741cbfe0f508e60
2024-07-05 15:40:52 -04:00
Ben Luddy 2a1df23596 Support nondeterministic encode for the CBOR serializer.
Kubernetes-commit: de914d6e54cbd60ce267c2d6256771759854059b
2024-06-24 16:40:08 -04:00
liyuerich 73127496fc drop deprecated json/yaml newSerializers, use json.NewSerializerWithOptions instead
Signed-off-by: liyuerich <yue.li@daocloud.io>

Kubernetes-commit: 3c9309db463679c348934429d8487d190ed5e64a
2024-05-29 19:00:00 +08:00
David Eads cc2ba35af5 add field and label selectors to authorization attributes
Co-authored-by: Jordan Liggitt <liggitt@google.com>

Kubernetes-commit: 92e3445e9d7a587ddb56b3ff4b1445244fbf9abd
2024-05-23 15:12:26 -04:00
David Eads ce76a8f4c2 generate
Kubernetes-commit: f5e5bef2e045679acd602500e7be4d676e72607c
2024-06-05 16:50:43 -04:00
David Eads 35052c5eef add subjectaccessreview field and label selectors
Co-authored-by: Jordan Liggitt <liggitt@google.com>

Kubernetes-commit: 90f0b88b6a5855054460f45edc369f1edea0b97f
2024-06-04 17:58:59 -04:00
Kubernetes Publisher ab0686929a Merge pull request #126105 from benluddy/cbor-framer
KEP-4222: Implement runtime.Framer for CBOR Sequences.

Kubernetes-commit: 611cbbf64b6ef8b73bb4a1f6dc275bef28287719
2024-07-15 13:11:09 -07:00
Ben Luddy 429f4e4400 Implement runtime.Framer for CBOR Sequences.
Kubernetes-commit: e2b36a0f0c3801085d765ad5155c8d08be9ed09c
2024-07-10 17:07:46 -04:00
Kubernetes Publisher d7e1c53111 Merge pull request #126018 from aroradaman/bump-k8s-utils
bump k8s.io/utils

Kubernetes-commit: 46aa8959a0659e22c924bb52b38385d441715b2b
2024-07-13 18:25:33 +00:00
Kubernetes Publisher 07cb122d28 Merge pull request #125748 from benluddy/cbor-custom-marshalers
KEP-4222: Check for and reject unsupported custom marshalers and unmarshalers.

Kubernetes-commit: 1a19d5102c5e1da1779b9daa8fbd82b23c974ebf
2024-07-12 17:16:54 -07:00
Daman Arora dd17456c37 bump k8s.io/utils
Signed-off-by: Daman Arora <aroradaman@gmail.com>

Kubernetes-commit: c6a129b715646163ef83f94245c3756cbc191c42
2024-07-12 14:40:22 +05:30
Ben Luddy c485170e5f Error on custom (un)marshalers without a CBOR implementation.
When CBOR marshaling or unmarshaling a type that implements any of TextMarshaler, TextUnmarshaler,
json.Marshaler, or json.Unmarshaler, without also implementing the corresponding CBOR interface, the
proposed behavior is to automatically use the available interface method by transcoding between CBOR
and JSON or text. As a safety measure, values of these types will be rejected by the CBOR serializer
until that is implemented.

Kubernetes-commit: 40c283908358c7af82f14ba3ea77960d5caf42d9
2024-04-30 17:35:23 -04:00
Kubernetes Publisher 4524748494 Merge pull request #125790 from benluddy/cbor-fieldsv1
KEP-4222: Support either JSON or CBOR in FieldsV1.

Kubernetes-commit: ccbbbc0f1f353e7dec5ce3dd83cccc0b7603d40a
2024-07-11 22:25:37 +00:00
Kubernetes Publisher 6b362fab6d Merge pull request #125676 from benluddy/cbor-bufferpool
KEP-4222: Don't pool large CBOR encode buffers

Kubernetes-commit: c3c8a9cfd47b238805e6bba6677c2b0b809ec8e6
2024-07-11 02:25:42 +00:00
Kubernetes Publisher 56f28d166a Merge pull request #125629 from benluddy/cbor-rawextension
KEP-4222: Automatically transcode RawExtension between unstructured protocols.

Kubernetes-commit: 2d877b5259d24fcf5545984fc461100ffa061b84
2024-07-10 14:54:35 -07:00
Ben Luddy 5976b0078c Don't pool large CBOR encode buffers.
Objects in a sync.Pool are assumed to be fungible. This is not a good assumption for pools
of *bytes.Buffer because a *bytes.Buffer's underlying array grows as needed to accomodate writes. In
Kubernetes, apiservers tend to encode "small" objects very frequently and much larger
objects (especially large lists) only occasionally. Under steady load, pooled buffers tend to be
borrowed frequently enough to prevent them from being released. Over time, each buffer is used to
encode a large object and its capacity increases accordingly. The result is that practically all
buffers in the pool retain much more capacity than needed to encode most objects.

As a basic mitigation for the worst case, buffers with more capacity than the default max request
body size are never returned to the pool.

Kubernetes-commit: a19d142f0da60841887bb4be4f33e7b99a2b0ea8
2024-06-14 15:51:52 -04:00
Ben Luddy 3da83feac5 Support either JSON or CBOR in FieldsV1.
The value of FieldsV1 is dependent upon its serialization. All existent FieldsV1 usage can safely
assume either JSON null or a JSON object. This is notably different from RawExtension, which might
hold arbitrary bytes.

To retain backwards compatibility for existing programs, FieldsV1 values decoded from CBOR will be
automatically transcoded to JSON. This will follow the same opt-out and migration plan as
RawExtension.

Kubernetes-commit: 37071989a0b56674ca3e91c013fdbc6a01808648
2024-06-14 12:59:48 -04:00
Ben Luddy e55063d289 Automatically transcode RawExtension between unstructured protocols.
Kubernetes-commit: 4755e1f85979f4db11114261797e6da8b116dc10
2024-06-14 12:59:48 -04:00
Kubernetes Publisher e126c655b8 Merge pull request #125743 from benluddy/extract-roundtrip-to-unstructured
Extract RoundtripToUnstructured to apimachinery apitesting library.

Kubernetes-commit: 53c8efbe711607417c07c07783b3168bd9d5a943
2024-07-08 18:25:26 +00:00
Kubernetes Publisher c1f2403557 Merge pull request #125922 from dims/update_otel_27
Update opentelemetry dependencies to the latest release (Take 2)

Kubernetes-commit: 07cc20a7509e7322e6ebb04e60d8274f27d6fdd7
2024-07-07 02:25:28 +00:00
Kubernetes Publisher f813d28092 Merge pull request #125835 from benluddy/roundtrip-error-fmt-strings
Fix fmt verbs for strings in roundtrip test errors

Kubernetes-commit: 8d0ee91fc74ce16e51f32438d32287bf0aecdff5
2024-07-06 05:02:53 -07:00
Davanum Srinivas 9ac6d1794d update OpenTelemetry dependencies and grpc
This update dropped the otelgrpc → cloud.google.com/go/compute dependency,
among others. This dropped out because genproto cleaned up it's dependencies
on google cloud libraries, and otel updated - details in #113366.

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
Co-Authored-By: David Ashpole <dashpole@google.com>

Kubernetes-commit: ff7942be83ed0c0aaa8c258e8e2b9965d383935c
2024-07-05 12:10:07 -04:00
Ben Luddy 160885f33f Fix fmt verbs for strings in roundtrip test errors.
Some of the calls to t.Errorf from apitesting/roundtrip use the %#v verb (Go-syntax representation)
for string arguments that are have already been formatted for legibility by something like like
cmp.Diff. Quoting and escaping newlines in these strings makes them harder to read, like this:

> ...
> "(*storage.CSIDriver)({\n TypeMeta: (v1.TypeMeta) {\n Kind: (string) \"\",\n APIVersion: (string)
> \"\"\n },\n ObjectMeta: (v1.ObjectMeta) {\n Name: (string) (len=15) \"犱âM邽[ǎ*ʄ\",\n
> ...

Kubernetes-commit: 6c18d0ec2e01c69c79a005e8ce4194567b945cc2
2024-07-01 21:23:19 -04:00
Matthieu MOREL ef4453d261 fix: enable bool-compare rule from testifylint linter (#125135)
* fix: enable bool-compare rule from testifylint linter

Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>

* Update hack/golangci.yaml.in

Co-authored-by: Patrick Ohly <patrick.ohly@intel.com>

* Update golangci.yaml.in

* Update golangci-strict.yaml

* Update golangci.yaml.in

* Update golangci.yaml.in

* Update golangci.yaml.in

* Update golangci.yaml.in

* Update golangci.yaml

* Update golangci-hints.yaml

* Update golangci-strict.yaml

* Update golangci.yaml.in

* Update golangci.yaml

* Update mux_test.go

---------

Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
Co-authored-by: Patrick Ohly <patrick.ohly@intel.com>

Kubernetes-commit: 0cde5f1e28c77496ba895ac44aff92a720c5f9c0
2024-06-28 19:58:05 +02:00
Kubernetes Publisher adf72dd6c5 Merge pull request #125759 from dims/bump-prometheus/common-v0.55.0
Bump `prometheus/common` to v0.55.0

Kubernetes-commit: 4c44efe81c9a26d66cdf88e917aeee75dad12299
2024-06-28 06:19:34 +00:00
Kubernetes Publisher 1dfa5d9369 Merge pull request #125766 from dims/update-moby/spdystream-to-v0.4.0
Update moby/spdystream to v0.4.0

Kubernetes-commit: 742b2f70b9e5c1ec03682ad25dc76fdcc7109310
2024-06-27 22:19:29 +00:00
Kubernetes Publisher a0fb8b1e7e Merge pull request #125646 from HirazawaUi/apply-null
Prune explicit nulls from client-side apply create

Kubernetes-commit: 991e7a8c15cbf959cd67bf92fd5e8adfd6875406
2024-06-27 18:19:36 +00:00
Davanum Srinivas a2e9f2d540 Update moby/spdystream to v0.4.0
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 377a3f7ec4dc2b5e09e0aadb651999d400c31538
2024-06-27 13:07:47 -04:00
Davanum Srinivas 6deaf269b3 Bump `prometheus/common to` v0.55.0
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 35ccdc8b35f1c4346071d4ff0efecdd7a6bcdecc
2024-06-27 07:58:24 -04:00
Kubernetes Publisher 65a3763a09 Merge pull request #125712 from benluddy/remove-cbor-test-skips
KEP-4222: Remove temporary mechanism for skipping CBOR tests.

Kubernetes-commit: 152a0ca4745b20928386827cfc6c8b52d210052a
2024-06-27 02:19:49 +00:00
Kubernetes Publisher 276559d398 Merge pull request #125745 from BenTheElder/ping-ping
bump  github.com/moby/spdystream to v0.3.0

Kubernetes-commit: 11446a394fb851d3496d31d96a67f8fcba6348e3
2024-06-26 22:19:53 +00:00
Benjamin Elder 11ede0a23b bump github.com/moby/spdystream to v0.3.0
picks up fix for data-race in Ping

Kubernetes-commit: c5aa8fdc711982dd589a9ac940b05297cc46b4a5
2024-06-26 12:27:14 -07:00
Kubernetes Publisher 93912e7e09 Merge pull request #125422 from benluddy/cbor-disable-binarymarshaler
KEP-4222: Disable recognition of Binary(Unm|M)arshaler in CBOR serializer.

Kubernetes-commit: 8637867c9cb664df864329c8ade9cac1fd777b7f
2024-06-26 22:19:52 +00:00
Ben Luddy 04bc058e89 Extract RoundtripToUnstructured to apimachinery apitesting library.
This will allow the same scenarios to be exercised on types defined in staging modules, like
apiextensions-apiserver, without importing them all from the root module.

Kubernetes-commit: 532471618ec85f0ab63d801ca8fbb32444076e68
2024-06-26 15:02:58 -04:00
Kubernetes Publisher 01e80c94aa Merge pull request #125731 from dashpole/revert_otel
Revert "Update opentelemetry dependencies to the latest release."

Kubernetes-commit: a4b8d0faa8e7d3227cbdda39241998d38f1c294e
2024-06-26 18:19:34 +00:00
David Ashpole 5f5d4bfd68 Revert "update OpenTelemetry dependencies"
This reverts commit 82e9ce79c763f1028f542b1246114082430e6b20.

Kubernetes-commit: e94047c9002c17a3b76513c3cde2d53aed39b7fb
2024-06-26 14:13:33 +00:00
Kubernetes Publisher 9501ff91ec Merge pull request #125419 from benluddy/cbor-byteslice-base64
KEP-4222: Enable JSON-compatible base64 encoding of []byte for CBOR.

Kubernetes-commit: 01f9712c6f10daf950694fdaca99bcdd2d724323
2024-06-26 10:19:33 +00:00
Kubernetes Publisher c225984b7b Merge pull request #122891 from siyuanfoundation/api-comp-ver1
apimachinery: API Emulation Versioning

Kubernetes-commit: 7a6062f4c13d0b7407876b325ce52c43de18f92f
2024-06-26 06:14:46 +00:00
Kubernetes Publisher a05248b07a Merge pull request #125421 from benluddy/cbor-simple-values
KEP-4222: Reject CBOR simple values other than true, false, and null.

Kubernetes-commit: 727fe1671b6bf22daca608ee03c8267d9164ad3e
2024-06-26 06:14:45 +00:00
Kubernetes Publisher 123bf3a821 Merge pull request #125669 from benluddy/cbor-bump-v2.7.0
KEP-4222: Bump github.com/fxamacker/cbor/v2 to v2.7.0.

Kubernetes-commit: beb48b7f5df83cd56275f471e52ef588ba845093
2024-06-26 06:14:44 +00:00
Kubernetes Publisher e94cfc0e8b Merge pull request #125418 from benluddy/cbor-byte-array-to-array
KEP-4222: Encode byte array to CBOR as array of integer, not byte string.

Kubernetes-commit: d198961730520e7ad1f9986c54e995d998fa2ca3
2024-06-26 06:14:44 +00:00
Kubernetes Publisher e3238d482d Merge pull request #125575 from dashpole/update_otel_27
Update opentelemetry dependencies to the latest release.

Kubernetes-commit: 535e833aef9718d1a19a8f71b3a4639fc92aa855
2024-06-26 06:14:43 +00:00
Ben Luddy 9731e9369f Remove temporary mechanism for skipping CBOR tests.
The CBOR decode and "appendix a" unit tests cover specific serialization behaviors that were known
to be incomplete at the time. Now that all of those cases have been addressed, the mechanism for
skipping those tests can be removed.

Kubernetes-commit: 2029bba6348a80f615e1c0224ec776d93069ea1b
2024-06-25 21:12:54 -04:00
Kubernetes Publisher 0e02b52b89 Merge pull request #125420 from benluddy/cbor-bignum-bigint
KEP-4222: Reject math/big.Int on encode and bignum tags on decode for CBOR.

Kubernetes-commit: 57645002432c52aa743e69739613ca337616441a
2024-06-24 15:46:38 -07:00
Ben Luddy e83773192a Bump github.com/fxamacker/cbor/v2 to v2.7.0.
Kubernetes-commit: dbe4c093d9f5b85fa509042556edf61fb6503b22
2024-06-24 09:49:40 -04:00
HirazawaUi 0daad00822 add comment for mergeMap
Kubernetes-commit: e4aff1b2ebabc83db08807225b74c829c8496c2f
2024-06-23 19:22:53 +08:00
David Ashpole 347a0045b0 update OpenTelemetry dependencies
Kubernetes-commit: 82e9ce79c763f1028f542b1246114082430e6b20
2024-06-19 00:43:16 +00:00
Ben Luddy 5626c83141 Disable recognition of Binary(Unm|M)arshaler in CBOR serializer.
The underlying CBOR library will by default encode a value to and from byte string if its type
implements encoding.BinaryMarshaler or encoding.BinaryUnmarshaler, respectively. This is now
disabled via an option to avoid diverging from JSON in those cases.

Kubernetes-commit: cc5a18678f7c1fdc945952145dcdd2b1bb03251a
2024-06-10 11:46:01 -04:00
Ben Luddy 042376f391 Encode byte array to CBOR as array of integer, not byte string.
This is structurally compatible with the JSON behavior.

Kubernetes-commit: 037ba12551d9adc1222d1146f73f69912b6bfa94
2024-06-10 11:30:03 -04:00
Ben Luddy 541e43763a Enable JSON-compatible base64 encoding of []byte for CBOR.
The encoding/json package marshals []byte to a JSON string containing the base64 encoding of the
input slice's bytes, and unmarshals JSON strings to []byte by assuming the JSON string contains a
valid base64 text.

As a binary format, CBOR is capable of representing arbitrary byte sequences without converting them
to a text encoding, but it also needs to interoperate with the existing JSON serializer. It does
this using the "expected later encoding" tags defined in RFC 8949, which indicate a specific text
encoding to be used when interoperating with text-based protocols. The actual conversion to or from
a text encoding is deferred until necessary, so no conversion is performed during roundtrips of
[]byte to CBOR.

Kubernetes-commit: 38f87df0e31efac2742382c2ddd51b8d5276978f
2024-06-10 09:50:01 -04:00
Siyuan Zhang f7e861d51d Add version mapping in ComponentGlobalsRegistry.
Signed-off-by: Siyuan Zhang <sizhang@google.com>

Kubernetes-commit: 4352c4ad2762ce49ce30e62381f8ceb24723fbcc
2024-05-31 20:29:48 -07:00
Ben Luddy 97723bf975 Reject math/big.Int on encode and bignum tags on decode for CBOR.
Although CBOR can roundtrip arbitrary precision integers, they don't roundtrip properly through
Unstructured because Unstructured is limited to the dynamic numeric types int64 and float64. Rather
than serializing them (and potentially losing information), reject them explicitly with an error.

Kubernetes-commit: 61654e9f5da57d1b44a8b4595b4d10275079a726
2024-05-15 13:49:42 -04:00
Kubernetes Publisher eb26334eeb Merge pull request #125423 from benluddy/cbor-nan-inf
KEP-4222: Reject NaN or infinite floating-point values in the CBOR serializer.

Kubernetes-commit: 4cd4e35061862c71df947225a22bf9b809c9ae41
2024-06-20 22:04:12 +00:00
Kubernetes Publisher 04fe5186a7 Merge pull request #125531 from pohly/klog-update
dependencies: klog v2.130.1

Kubernetes-commit: 44446e1c9c2e7f50061f2a998c76f6f55f3ca737
2024-06-20 18:04:12 +00:00
Patrick Ohly aab19dba9b dependencies: klog v2.130.1
Kubernetes-commit: f98e5d1dfcaa37fee2c394436583038cf3ff1e72
2024-06-16 14:04:43 +02:00
Kubernetes Publisher af4f0d893a Merge pull request #125424 from benluddy/cbor-timetag-rfc3339
KEP-4222: Decode CBOR time tags to interface{} as RFC 3339 timestamps.

Kubernetes-commit: a81ea5d0af41a7e519a06780aced3c517f61547c
2024-06-19 06:04:07 +00:00
Kubernetes Publisher 30b7bf1145 Merge pull request #125472 from karlkfi/karl-watch-comments
Add details to watch interface method comments

Kubernetes-commit: b498eb97406f780fbbeb39204f50bc473e64af56
2024-06-13 03:11:52 -07:00
Karl Isenberg bcc21841b7 Add details to watch interface method comments
The watch.Interface design is hard to change, because it would break
most client-go users that perform watches. So instead of changing the
interface to be more user friendly, this change updates the method
comments to explain the different responsibilities of the consumer
(client user) and the producer (interface implementer).

Kubernetes-commit: 1f35231a1d4f7b8586a7ec589c799729eeb4f7c4
2024-06-12 13:06:22 -07:00
Kubernetes Publisher 1a6a62ad18 Merge pull request #125408 from benluddy/bump-cbor-v2.7.0
KEP-4222: Bump github.com/fxamacker/cbor/v2.

Kubernetes-commit: 6346b9d1327c4b8be2398d9715bdae5475e27569
2024-06-11 00:33:33 +00:00
Ben Luddy cf52dcb434 Reject NaN or infinite floating-point values in the CBOR serializer.
Kubernetes-commit: 2a31354e266e829d6d63a776b933947b4118436e
2024-05-09 15:19:51 -04:00
Ben Luddy cfa284d64b Decode CBOR time tags to interface{} as RFC 3339 timestamps.
Kubernetes-commit: a246eb19e5f801f05eeffb2bbf73ad25cb0c1d1c
2024-05-09 15:09:01 -04:00
Ben Luddy 8e6f2d450e Reject CBOR simple values other than true, false, and null.
Kubernetes-commit: 326e0a44d1a2158f8e9bc32cc2f1e4ecb50354a8
2024-05-09 15:02:37 -04:00
Ben Luddy 71aae7d7b6 Allow decoding RFC 3339 CBOR strings to time.Time.
We had been relying on a bug in the library when it should have been rejected by default. That bug
has been fixed and a new option added to opt-in to the behavior we need.

Kubernetes-commit: b24defacc5c05ee1c991d4c74b6325fa3f77460f
2024-05-09 14:37:06 -04:00
Ben Luddy a2d2122e24 Bump fxamacker/cbor/v2 to v2.7.0-beta.
This library release makes a number of behaviors configurable in ways that are required for CBOR
support in Kubernetes.

Kubernetes-commit: c4279660cad039bc15495311cf7863640b6308f9
2024-05-09 14:30:58 -04:00
Kubernetes Publisher 703232ea6d Merge pull request #125299 from karlkfi/karl-reflector-fix-2
Improve Reflector unit tests

Kubernetes-commit: 5bf1e95541d90e37f6c6637b5b45d8783e7907aa
2024-06-03 16:42:08 -07:00
Karl Isenberg 8ac23fa094 Improve Reflector unit tests
- Add tests to confirm that Stop is always called.
- Add TODOs to show were Stop is not currently being called
  (to fix in a future PR)

Kubernetes-commit: ab5aa4762fd5206d0dbd8412d7c6f3b76533a122
2024-06-03 12:15:38 -07:00
Kubernetes Publisher 733a95eb52 Merge pull request #122832 from benluddy/cbor-fuzz-native-to-unstructured-via
KEP-4222: Add roundtrip tests to Unstructured via CBOR and JSON.

Kubernetes-commit: f30a87d517c538f22e1d84b4d7c497f57f7b760a
2024-05-30 15:00:31 -07:00
Ben Luddy b4069ae172 Implement cbor.Marshaler and cbor.Unmarshaler for resource.Quantity.
Kubernetes-commit: d2dfce59ff60bd568dbb67282125e7e35ada77b2
2024-05-17 11:52:40 -04:00
Ben Luddy 6253e16189 Implement cbor.Marshaler and cbor.Unmarshaler for metav1.MicroTime.
Kubernetes-commit: 14367eee5a2516a3ab7a960a828e95bafb1ff0e2
2024-05-17 11:52:30 -04:00
Ben Luddy b19cb35fc0 Implement cbor.Marshaler and cbor.Unmarshaler for metav1.Time.
Kubernetes-commit: 7b3129e015eb7fefa0749a45911540946b389e74
2024-05-17 11:52:17 -04:00
Ben Luddy bd5fa0b41e Implement cbor.Marshaler and cbor.Unmarshaler for IntOrString.
Kubernetes-commit: d93a9121b836b8e447d9e62621631a936cd35626
2024-05-17 11:51:48 -04:00
Kubernetes Publisher 63ab494c70 Merge pull request #123339 from skitt/canonical-json-patch
Update kustomize, use canonical json-patch v4 import

Kubernetes-commit: da02fdb2aef1b7102526963c91df4992ee5b6a05
2024-05-29 20:32:33 +00:00
Dan Winship fe80b851d8 Add utilvalidation.GetWarningsForIP and .GetWarningsForCIDR
(And port the existing Service warnings to use them.)

Kubernetes-commit: 610adebdb75082c8d643354ca96e66f3b2bc3773
2024-02-16 12:07:56 -05:00
Stephen Kitt 845ea7eba3 Use canonical json-patch v4 import
The canonical import for json-patch v4 is
gopkg.in/evanphx/json-patch.v4 (see
https://github.com/evanphx/json-patch/blob/master/README.md#get-it for
reference).

Using the v4-specific path should also reduce the risk of unwanted v5
upgrade attempts, because they won't be offered as automated upgrades
by dependency upgrade management tools, and they won't happen through
indirect dependencies (see
https://github.com/kubernetes/kubernetes/pull/120327 for context).

Signed-off-by: Stephen Kitt <skitt@redhat.com>

Kubernetes-commit: 5300466a5c8988b479a151ceb77f49dd00065c83
2024-02-16 13:57:24 +01:00
Stephen Kitt a14e568d76 Update kubectl kustomize to kyaml/v0.17.1, cmd/config/v0.14.1, api/v0.17.2, kustomize/v5.4.2
Signed-off-by: Stephen Kitt <skitt@redhat.com>

Kubernetes-commit: 33c6f6bc65395aa514c9cf17115a1c63564c22e7
2024-05-27 17:42:29 +02:00
Kubernetes Publisher d8a3da39bf Merge pull request #125045 from pohly/ginkgo-gomega-update
dependencies: ginkgo v2.19.0, gomega v1.33.1

Kubernetes-commit: 1c84623028b496e22d8401100ef6f59325e092e0
2024-05-28 08:32:27 +00:00
Kubernetes Publisher 5c8637dbd9 Merge pull request #124775 from benluddy/cbor-unstructuredlist
KEP-4222: Decode CBOR to UnstructuredList as UnstructuredJSONScheme does.

Kubernetes-commit: c9cfc74fd594ffff3b73ec35902d405f13653d8b
2024-05-23 04:32:13 +00:00
Kubernetes Publisher 491adfd947 Merge pull request #125068 from benluddy/cbor-decode-to-any
KEP-4222: Add unit tests for decoding CBOR into interface{} type

Kubernetes-commit: 06ba3d8ab6d76e420d6380a5d0f9fd0c162949ce
2024-05-22 15:43:50 -07:00
Patrick Ohly 48701d2755 dependencies: ginkgo v2.19.0, gomega v1.33.1
Ginkgo v2.18.0 allows tweaking the output so that
it's easier to follow while a job runs in
Prow (https://github.com/onsi/ginkgo/issues/1347). Using this in
hack/ginkgo-e2e.sh will follow in a separate commit.

Gomega gets bumped to the latest release to keep it up-to-date.

Ginkgo v1.19.0 adds support for --label-filter with labels that represent
sets (like our Feature:<Foo>).

Kubernetes-commit: 37e2dd6857084a172ef5210caee1fefa8dd8159a
2024-05-22 10:22:09 +02:00
Ben Luddy 7f39f0913d Decode CBOR to UnstructuredList as UnstructuredJSONScheme does.
Decoding to map[string]interface{} and passing the result to UnstructuredList's
SetUnstructuredContent method does not produce objects that are identical to those produced by
UnstructuredJSONScheme's decode method. UnstructuredJSONScheme's decode:

1. removes the "items" key from the map in its Object field

2. sets "apiVersion" and "kind" (determined heuristically from the list's GVK) on elements of its
Items slice that were not serialized with a nonempty string "apiVersion" and "kind"

3. returns a missing kind error if any element is missing "kind"

Kubernetes-commit: 7e6b8663afa1245ca6348bdd8ba231201c5786da
2024-05-09 14:15:46 -04:00
Suriyan S 60bddd4548 Add unit tests for decoding CBOR into interface{} type
Co-authored-by: Ben Luddy <bluddy@redhat.com>

Kubernetes-commit: 3a097f224aef87ac5261a5f9cdd37dec48030cb8
2024-02-27 21:28:52 -05:00
Kubernetes Publisher 1da46c3f5a Merge pull request #124799 from benluddy/cbor-self-described-cbor-tag-decode-test
KEP-4222: Add CBOR decoder unit test that accepts tag 55799.

Kubernetes-commit: 925cb2be3030fb93008e6fc3cb03da2db0504c59
2024-05-10 15:43:18 -07:00
Ben Luddy 27aebe7e05 Add CBOR decoder unit test that accepts tag 55799.
Tag 55799 (self-described CBOR) imparts no special semantics on the item it encloses. The CBOR
encoder always encloses its output in this tag so that the prefix 0xd9d9f7 can be used to
mechanically distinguish encoded CBOR from encoded JSON, and the decoder must be able to accept any
sequence of bytes that the encoder can produce.

Kubernetes-commit: 19921cbf0de03cd6a822b6e16e371c0d45cb7c20
2024-05-10 10:57:17 -04:00
Kubernetes Publisher 53e91cb5d2 Merge pull request #123620 from benluddy/json-frame-reader-underlying-array-reuse
Remove shared ref to underlying array of JSONFrameReader's Read arg.

Kubernetes-commit: 5cb71ec2e4c506078449b6a3bbaac44ab03909ce
2024-05-09 20:24:40 +00:00
Kubernetes Publisher c9c3e94f52 Merge pull request #121496 from benluddy/metav1-labelselector-fuzz
Deduplicate set expression values in metav1.LabelSelector fuzzer.

Kubernetes-commit: 99129ed5ebbf435e65e7416f166558e37b1aac4d
2024-05-03 20:24:09 +00:00
Kubernetes Publisher d5c9711b77 Merge pull request #124562 from sbueringer/pr-bump-sigs-yaml
Bump sigs.k8s.io/yaml to v1.4.0

Kubernetes-commit: c1ef6c44f5d7b582bf19669c6dbf2ff9552b9d6c
2024-04-29 21:32:36 +00:00
Kubernetes Publisher a043c3f43f Merge pull request #122026 from skitt/sets-go1.21
Use Go 1.21 Ordered and clear for sets

Kubernetes-commit: 84ac8a1bd23f79d6fe3a300e86926e749b2a5e66
2024-04-29 21:32:36 +00:00
Stefan Bueringer e2d25ee4dc Bump sigs.k8s.io/yaml to v1.4.0
Kubernetes-commit: 04cc45b4adda1b19d5067d45ed246c0f84fed966
2024-04-26 15:28:17 +02:00
Kubernetes Publisher 03f2f3350d Merge pull request #124469 from serathius/etcd-3.5.13
Upgrade etcd libraries to v3.5.13

Kubernetes-commit: 0f063280964b09e0e21c8cc457a181c20c68da61
2024-04-24 17:32:19 +00:00
Marek Siarkowicz 281251e66b Upgrade etcd libraries to v3.5.13
Add otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents) to tracing options due to https://github.com/open-telemetry/opentelemetry-go-contrib/pull/3964

Kubernetes-commit: 3e5b03eb433ee359782f5aa6e9368ab2a0d0370c
2024-04-23 11:10:37 +02:00
Kubernetes Publisher bb8822152c Merge pull request #124328 from jiahuif-forks/deps/cel-go
bump cel-go to v0.20.1 and refit CEL libraries

Kubernetes-commit: 16a594f907d0d4a6224dab2d0704793d5e7898f6
2024-04-23 01:32:16 +00:00
Kubernetes Publisher bfd47a16b8 Merge pull request #123626 from benluddy/cbor-test-encode-no-duplicate-map-key
KEP-4222: Add tests for CBOR encoder handling of duplicate field names/tags.

Kubernetes-commit: e2bd80aeb16466c793a83b79bc79303522a9dfdc
2024-04-23 01:32:15 +00:00
Jiahui Feng 952d3cb917 generated: ./hack/update-vendor.sh
Kubernetes-commit: 350fcf957e90501f0b224b7ccf771b29d4d5c6b6
2024-04-22 10:54:32 -07:00
Kubernetes Publisher 0ee3e61508 Merge pull request #124324 from benluddy/cbor-decode-tests-grouped-by-cbor-type
KEP-4222: Group CBOR decode tests by the kind of their inputs.

Kubernetes-commit: 77f7d1b89dafbf36332aae230a28d5cd360dac17
2024-04-18 13:32:08 +00:00
Kubernetes Publisher ea31e51502 Merge pull request #124116 from HiranmoyChowdhury/hiranmoy
Fix Deep Copy issue in getting controller reference

Kubernetes-commit: 3261821fbcb99a34f7f442ff80e8f706734ee322
2024-04-18 13:32:08 +00:00
Kubernetes Publisher 8c36da9e60 Merge pull request #121970 from pohly/log-apimachinery-runtime
apimachinery runtime: support contextual logging

Kubernetes-commit: d35ba3635b0fe9cee75e2376e6445113d50437b7
2024-04-18 09:32:09 +00:00
Kubernetes Publisher 77786464c2 Merge pull request #116781 from muff1nman/protobuf-fully-qualified-types
generate fully qualified type references

Kubernetes-commit: 0dc45103d879c3b280671f009d3f830650903894
2024-04-18 01:32:06 +00:00
Jiahui Feng 125cb5feb4 generated: ./hack/pin-dependency.sh github.com/google/cel-go v0.20.1
Kubernetes-commit: 94997c6fefa2791192d0a7ab68b02bf5d8b6c2c5
2024-04-15 13:33:10 -07:00
Ben Luddy d39475baae Group CBOR decode tests by the kind of their inputs.
No test cases are added, removed, or modified. Grouping them this way is intended to make it easier
to reason about the coverage for possible inputs versus one long list of test cases.

Kubernetes-commit: c985a0a0f655b35ead79bbde655955d57a76d478
2024-04-15 11:50:18 -04:00
Hiranmoy Das Chowdhury 8d006f4a67 deep copy issue in getting controller is solved
Kubernetes-commit: 0cd2588d4f7a7776f9a75e6ac39cd6ea99c16a3e
2024-04-09 11:14:33 +06:00
Kubernetes Publisher e696ec55a3 Merge pull request #124174 from dims/update-x/net-for-CVE-2023-45288
Update x/net for CVE-2023-45288

Kubernetes-commit: d9c54f69d4bb7ae1bb655e1a2a50297d615025b5
2024-04-04 03:52:54 +00:00
Davanum Srinivas 2dd88022cc Update x/net for CVE-2023-45288
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 99fac38d2864e6bc9bb7cd1743d658caa1360c0c
2024-04-03 16:37:18 -04:00
Kubernetes Publisher d82afe1e36 Merge pull request #123801 from HirazawaUi/followup-allow-special-characters
Follow-up on unresolved question in PR #123385

Kubernetes-commit: ebf4ca686a3a0e8955fe852a05741ca21c2f7680
2024-03-07 09:18:17 -08:00
HirazawaUi 0407311be5 followup to allow special characters
Kubernetes-commit: 2867be47b3162109391b4b2ae4f70f43e4c8fb34
2024-03-08 00:08:43 +08:00
Kubernetes Publisher 25164f7745 Merge pull request #123435 from tallclair/apparmor-ga
AppArmor fields API

Kubernetes-commit: bd25605619cbfb46b075002a6db58b4e489fc8cb
2024-03-07 04:48:02 +00:00
Kubernetes Publisher cbfe0a1fea Merge pull request #123758 from liggitt/protobump
[CVE-2024-24786] Bump github.com/golang/protobuf v1.5.4, google.golang.org/protobuf v1.33.0

Kubernetes-commit: a5f5f44157c49fdfb6384862c7cb34c2ddbd4cce
2024-03-06 16:48:12 +00:00
Jordan Liggitt 21d26b6fd4 Bump github.com/golang/protobuf v1.5.4, google.golang.org/protobuf v1.33.0
Kubernetes-commit: c6673d2346c814ddb4629c569bdc659ffa0c583f
2024-03-06 09:47:28 -05:00
Kubernetes Publisher 0c29f846b5 Merge pull request #123385 from HirazawaUi/allow-special-characters
Allow almost all printable ASCII characters in environment variables

Kubernetes-commit: 87f9b3891e7566aa085645aac4e5e3b4379b4efd
2024-03-06 04:48:09 +00:00
Kubernetes Publisher 60d24f246f Merge pull request #123708 from p0lyn0mial/upstream-const-watchlist-bookmark-event
apimachinery/meta/types.go: define InitialEventsAnnotationKey const

Kubernetes-commit: b0ee3343747520db7e41c298d566026cd8df0be8
2024-03-05 10:40:51 -08:00
Lukasz Szaszkiewicz 513d23a10f apimachinery/meta/types.go: define InitialEventsAnnotationKey const
InitialEventsAnnotationKey the name of the key
under which an annotation marking the end of
a watchlist stream is stored.
The annotation is added to a "Bookmark" event.

The const will be immediately used in client-go and apiserver packages.

Kubernetes-commit: 3f7d4b787b74a2d110901d333f83e99c0e5e49c9
2024-03-05 11:52:25 +01:00
Kubernetes Publisher 67cb3a878c Merge pull request #123413 from seans3/tunneling-spdy-websockets
PortForward: Tunnel SPDY through WebSockets

Kubernetes-commit: f745503112e06d6ff199e929d536c6a29825c01a
2024-03-04 17:18:44 -08:00
Sean Sullivan 5ddec50274 removes extra upgrade aware proxy logging; returns tunneling connection close error
Kubernetes-commit: e8bbb221d36f1adf4116752990c0c4f17a9e5deb
2024-03-02 16:10:39 -08:00
Ben Luddy 948272cd0f Add tests for CBOR encoder handling of duplicate field names/tags.
Kubernetes-commit: 8df914ae87e4cda1a68740f7692db73a2cc621ac
2024-03-01 17:26:10 -05:00
Ben Luddy e79aa4beda Remove shared ref to underlying array of JSONFrameReader's Read arg.
When JSONFrameReader read a JSON object longer than the length of the destination slice, but not
larger than the capacity of the destination slice, it would retain a reference to a slice sharing
the same underlying array as the destination slice. If the underlying array is modified between
calls to Read, corrupt frame data could be returned.

This is also called out in the io.Reader contract: "Implementations must not retain p."

Kubernetes-commit: 10f7a166dd236a7db142e67a2121fe8465014394
2024-02-29 19:21:16 -05:00
Tim Allclair f57af5f175 Stop appending AppArmor status to node ready condition
Kubernetes-commit: 24537a91317f9fd125ee805cd0b781358ac86f35
2024-02-21 13:11:07 -08:00
Sean Sullivan 808e70831b portforward: tunnel spdy through websockets
Kubernetes-commit: 8b447d8c97e8823b4308eb91cf7d75693e867c61
2024-02-21 08:56:07 +00:00
Kubernetes Publisher df38a01ea7 Merge pull request #123536 from benluddy/cbor-roundtrip-unit-via-interface
KEP-4222: Make CBOR roundtrip tests pass through interface{}.

Kubernetes-commit: 55e4172a0d132c460bf362bb23a598729d87b7af
2024-03-02 00:47:25 +00:00
Kubernetes Publisher 8193f66554 Merge pull request #123436 from dinhxuanvu/cbor-tests
KEP-4222: Add duplicate key and field case-sensitivity CBOR decode tests.

Kubernetes-commit: 138f99b41a793710cc426b2d4ebba7f559b63700
2024-03-02 00:47:23 +00:00
Kubernetes Publisher c9969982b5 Merge pull request #123529 from thockin/go-workspaces
Go workspaces for k/k and k/staging/*

Kubernetes-commit: df366107d16aa2e2cdd620be41e592184f379da4
2024-03-01 20:44:47 +00:00
Kubernetes Publisher 6362b69e39 Merge pull request #123598 from liggitt/remotecommand-cleanup
Remotecommand test flake cleanup

Kubernetes-commit: 0d50a398df50b2b7c21ed45c5e186906bc548c7a
2024-02-29 13:40:48 -08:00
Jordan Liggitt 2161860b98 Avoid logging binary junk for frame write failure
Kubernetes-commit: fc86811cbe8fd030b1402a7f060b168771100a70
2024-02-29 15:09:34 -05:00
Tim Hockin df7ad6e136 Fix up go.mod files after reviews
Because of how the previous 100+ commits were done, so changes snuck
thru that properly belong in earlier commits but it's not really
possible to do that without a lot of effort.

We agreed it was OK to "spackle" these cracks with a final commit.

Kubernetes-commit: 21715e6bbd19c932576ff268843d8ead3edb05e4
2024-02-28 16:50:55 -08:00
Ben Luddy 59152b5959 Make CBOR roundtrip cases pass through interface{} as well.
Kubernetes-commit: 1503997b4f90b7fc567e1ca3bd5aa4d6318450f6
2024-02-27 16:14:32 -05:00
Ben Luddy 933fc00b5c Address review nit, use longer variable identifiers.
Kubernetes-commit: 374e4b56861b8c28831a2ac080dbbb970fdae6da
2024-02-27 16:12:39 -05:00
Tim Hockin 323f8c9aa1 Remove old gengo detritus
Kubernetes-commit: 812d5fff4011df4693dcdace516feec30ebff8ba
2024-02-26 23:31:41 -08:00
Kubernetes Publisher 0f2e935799 Merge pull request #123348 from hoskeri/update-go-x-crypto-19
Update x/crypto to 0.19.

Kubernetes-commit: 9a9028983806af26e7b48223f3a92922e94725df
2024-02-21 20:21:33 +00:00
Kubernetes Publisher 856aea55ac Merge pull request #123392 from thockin/depreciate
Cleanup: s/depreciated/deprecated/g

Kubernetes-commit: 11785bb815d58eb553be3a1fa305464c35d860cc
2024-02-21 12:21:45 +00:00
Kubernetes Publisher afd4b8fa42 Merge pull request #123268 from benluddy/cbor-appendix-a-tests
KEP-4222: Add roundtrip tests for all CBOR examples in RFC 8949 Appendix A.

Kubernetes-commit: 5275bb0502a40d6ebc2ad78f7fd3ec6f1dad3788
2024-02-21 00:22:54 +00:00
Kubernetes Publisher 5504fa764d Merge pull request #123267 from benluddy/cbor-marshaling-tests
KEP-4222: Add decode and roundtrip tests for CBOR marshaling.

Kubernetes-commit: 930f60173bd179bccac23a90f07c38bd9d32cc5e
2024-02-21 00:22:53 +00:00
HirazawaUi d4f2d34ce9 add relaxed env var name function
Kubernetes-commit: 96a16a7bc9f6430c67cb786799b49b7cb5551c7e
2024-02-19 22:08:50 +08:00
Tim Hockin 7f22e754ee Cleanup: s/depreciated/deprecated/g
Kubernetes-commit: 9f4b82bf3b079fe868effbd2498b61464db6d459
2024-02-18 14:50:55 -08:00
Abhijit Hoskeri d19b6bd81e Update x/crypto to 0.19.
Main reason is to pick up updated CA roots.

Full diff: https://github.com/golang/crypto/compare/v0.16.0...v0.19.0

Kubernetes-commit: d3a0e296defbb0b55e591e273004e79e7ebfb1fd
2024-02-16 20:18:14 +00:00
Kubernetes Publisher 2511177aa2 Merge pull request #123174 from danwinship/cidr-validation-cleanup
Make CIDR validation consistent

Kubernetes-commit: 91ee30074bee617d52fc24dc85132fe948aa5153
2024-02-16 04:21:22 +00:00
Vu Dinh b39fd7bae0 Add duplicate key and field case-sensitivity CBOR decode tests.
1. Decoding map with duplicate keys into struct or map produces error.
2. Decoding a map into a Go struct matches json field tag names case-sensitively.
3. When decoding a map into a Go struct, a case-insensitive match between a key and a json field tag
   name is treated the same as no match.

Signed-off-by: Vu Dinh <vudinh@outlook.com>

Kubernetes-commit: 4fe78a17dddffa2c44ae01927cbaf9a84023530b
2024-02-15 15:11:52 -05:00
Kubernetes Publisher 75cefea9ac Merge pull request #122881 from benluddy/cbor-serializer-only
KEP-4222: Add CBOR Serializer implementation.

Kubernetes-commit: 542fe510462005c5c5757bed34c6ae62b7b36180
2024-02-15 10:57:39 -08:00
Ben Luddy 895c28be93 Add roundtrip tests for all CBOR examples in RFC 8949 Appendix A.
The examples of encoded data items in Appendix A cover all major types and various alternative
representations, including different ways of encoding a data item's "argument" (Section 3), fixed
and indefinite-length containers (Section 3.2), and tags from the extended generic data
model (Section 2.1).

Kubernetes-commit: 871279978fc19ab32e6c33f76b386e6425c444b8
2024-02-13 11:07:58 -05:00
Ben Luddy c9af5af41d Add decode and roundtrip tests for CBOR marshaling.
Co-authored-by: Suriyan Subbarayan <suriyansub710@gmail.com>

Kubernetes-commit: 57fc5d24013d5105663485ac6b5f982bb57045e7
2024-02-13 11:05:19 -05:00
Ben Luddy 48cc8db123 Add CBOR Serializer implementation.
Kubernetes-commit: 066421f108c2d6e53c84387ade80bb97a5c819a8
2024-02-13 11:03:24 -05:00
Kubernetes Publisher 665c1a23c2 Merge pull request #123250 from benluddy/dep-bump-cbor-v2.6.0
Bump github.com/fxamacker/cbor/v2 to v2.6.0.

Kubernetes-commit: e305e773bbfe8c5bdf9c57881a875e168b004b8c
2024-02-15 01:18:28 +00:00
Ben Luddy fe0d4b338e Bump github.com/fxamacker/cbor/v2 to v2.6.0.
Kubernetes-commit: aac43dc96f2b679f0ab030fd3512c7e03b0f2df4
2024-02-12 15:46:17 -05:00
Kubernetes Publisher 4a1251b70e Merge pull request #121486 from benluddy/cbor-stub
KEP-4222: Add stub CBOR serializer.

Kubernetes-commit: 48228bf9dbac308f43abd59a53fdc069fbddee0f
2024-02-10 01:19:09 +00:00
José Carlos Chávez 046ab0dee2 chore: adds consistent vanity import to files and provides tooling for verifying and updating them. (#120642)
* chore: drops update vanity imports from script.

* chore: changes copyright year to 2024.

* chore: makes lint happy.

Kubernetes-commit: 6d6398ef9266abce3518a4c9a3d4e4d8feeffdc1
2024-02-08 13:33:30 +01:00
Kubernetes Publisher 62eada4a22 Merge pull request #123164 from liggitt/api-validation
Put validation utility packages used by API validation under API review

Kubernetes-commit: fae7ec4a37642060bf97dcffb24a7a55b3190485
2024-02-08 01:17:28 +00:00
Kubernetes Publisher 58e33aba78 Merge pull request #122931 from danwinship/ip-validation-cleanup
consistently use IsValidIP for IP validation

Kubernetes-commit: 052bce26f4f48bdccdcb7057d4401f8eaa1eff70
2024-02-06 15:58:15 -08:00
Jordan Liggitt d8a2eb9e01 Put validation utility packages used by API validation under API review
Kubernetes-commit: eceaed8c0755efc9e33af7403b16200014b3022c
2024-02-06 17:50:09 -05:00
Siyuan Zhang cc42a1530d apiserver: Add API emulation versioning.
Co-authored-by: Siyuan Zhang <sizhang@google.com>
Co-authored-by: Joe Betz <jpbetz@google.com>
Co-authored-by: Alex Zielenski <zielenski@google.com>

Signed-off-by: Siyuan Zhang <sizhang@google.com>

Kubernetes-commit: 403301bfdf2c7312591077827abd2e72f445a53a
2024-01-19 16:07:00 -08:00
Dan Winship 718670137b Fix IP/CIDR validation to allow updates to existing invalid objects
Ignore pre-existing bad IP/CIDR values in:
  - pod.spec.podIP(s)
  - pod.spec.hostIP(s)
  - service.spec.externalIPs
  - service.spec.clusterIP(s)
  - service.spec.loadBalancerSourceRanges (and corresponding annotation)
  - service.status.loadBalancer.ingress[].ip
  - endpoints.subsets
  - endpointslice.endpoints
  - networkpolicy.spec.{ingress[].from[],egress[].to[]}.ipBlock
  - ingress.status.loadBalancer.ingress[].ip

In the Endpoints and EndpointSlice case, if *any* endpoint IP is
changed, then the entire object must be valid; invalid IPs are only
allowed to remain in place for updates that don't change any IPs.
(e.g., changing the labels or annotations).

In most of the other cases, when the invalid IP is part of an array,
it can be moved around within the array without triggering
revalidation.

Kubernetes-commit: ad22c0d4954a760c0d0c1ffebb3de6c05c66ea4f
2023-12-28 11:30:45 -05:00
Dan Winship 72340d2cf7 Add legacy versions of IsValidIP/IsValidCIDR
Add validation.IsValidIPForLegacyField and
validation.IsValidCIDRForLegacyField, which validate "legacy" IP/CIDR
fields correctly. Use them for all such fields (indirectly, via a
wrapper in pkg/apis/core/validation that handles the
StrictIPCIDRValidation feature gate correctly).

Change IsValidIP and IsValidCIDR to require strict parsing and
canonical form, and update the IPAddr, ServiceCIDR, and
NetworkDeviceData validation to make use of them.

Kubernetes-commit: 692785d25b688f83db14c20f26d0b99e2e99adb8
2025-02-28 17:41:10 -05:00
Dan Winship 8aa42c4d2a Slightly improve EndpointSlice address validation
Because it used both IsValidIPv4Address and ValidateEndpointIP,
EndpointSlice validation produced duplicate error messages when given
an invalid IP. Fix this by calling IsValidIP first, and only doing the
other checks if that one fails.

Also, since no one else was using the IsValidIPv4Address and
IsValidIPv6Address methods anyway, just inline them into the
EndpointSlice validation, so we don't have to worry about "should they
do legacy or strict validation" later.

Kubernetes-commit: ba189de78ff13f6882e753138a5df07d6ab49433
2025-02-28 18:14:44 -05:00
Dan Winship 5b974f202d Add validation.IsValidInterfaceAddress
Split "ifaddr"-style ("192.168.1.5/24") validation out of IsValidCIDR.
Since there is currently only one field that uses this format, and it
already requires canonical form, IsValidInterfaceAddress requires
canonical form unconditionally.

Kubernetes-commit: fc4bb4fdb906d3120f048ae7ee422a130bf05e94
2025-02-28 16:16:51 -05:00
Dan Winship b81059e0f2 Add validation.IsValidCIDR
Move apivalidation.ValidateCIDR to apimachinery, and rename it and
change its return value to match the other functions.

Also, add unit tests.

(Also, while updating NetworkPolicy validation for the API change, fix
a variable name that implied that IPBlock.Except[] is IP-valued rather
than CIDR-valued.)

Kubernetes-commit: 7a56b6e3f7ae2896a05c869be1e24a0885ccffce
2023-12-27 09:44:45 -05:00
Dan Winship 89b9414522 Make validation.IsValidIP return a field.ErrorList for consistency
Kubernetes-commit: 519dd6887dc275f77d5be32a1f711ff71117c87d
2023-12-26 21:11:15 -05:00
Dan Winship cc2017ec38 Expand IsValidIP unit tests
Add more test cases, and merge the IsValidIP, IsValidIPv4Address and
IsValidIPv6Address tests together. (Any string that passes IsValidIP
should pass either IsValidIPv4Address or IsValidIPv6Address but not
both, and any string that fails IsValidIP should fail both
IsValidIPv4Address and IsValidIPv6Address.)

Kubernetes-commit: f999b24fad907fa6f566e088bf417e9d7b217403
2023-12-27 09:44:45 -05:00
Dan Winship 8d387a68aa Drop validation.IsValidSocketAddr
It's not used anywhere, and if someone was going to validate an
IP:port somewhere, they should think about exactly what they want
rather than just using this function. (E.g., validation should be
slightly different for an IP:port to bind to vs an IP:port to connect
to.)

Kubernetes-commit: 8fc691be940e73dac324e92c3207ed03df74b1ca
2024-01-23 08:25:52 -05:00
Kubernetes Publisher f14778da55 Merge pull request #122842 from pohly/klog-update
dependencies: klog v2.120.1

Kubernetes-commit: b27b56a46c4c1e6be0dc2b1a0230d86223a7e903
2024-01-18 21:16:38 +00:00
Kubernetes Publisher 942edc4441 Merge pull request #122839 from pohly/ginkgo-gomega-update
dependencies: ginkgo v2.15.0, gomega v1.31.0

Kubernetes-commit: c82da711b0e2184f851675aac4596bbd0f74763f
2024-01-18 21:16:37 +00:00
Patrick Ohly c2f97d1ea2 dependencies: klog v2.120.1
Kubernetes-commit: e2222f1e304831cbbc57b61afa373612297055fb
2024-01-18 16:58:40 +01:00
Patrick Ohly 4f03c3f8db dependencies: ginkgo v2.15.0, gomega v1.31.0
The main reason for updating is support for reporting the cause of context
cancellation: Ginkgo provides that information when canceling a context and
Gomega polling code includes that when generating a failure message.

Kubernetes-commit: 18f0af1f000f95749ca1ea075d62ca89e86bb7da
2024-01-18 12:45:55 +01:00
Kubernetes Publisher 02a41040d8 Merge pull request #122706 from pacoxu/klog-upgrade
bump klog to  v2.120.0

Kubernetes-commit: 823ecb58f68fbe0a4b37b32e11e75c6f2e0f467c
2024-01-11 21:16:23 +00:00
Paco Xu 4dd1d064b4 bump klog to v2.120.0
Kubernetes-commit: 3c86d21316c25b52a1cf3f9703a0bc2cbe97131c
2024-01-11 17:35:07 +08:00
Dan Winship ba7db196ed Split out IP validation functions into their own file
(No code changes.)

Kubernetes-commit: 34717000dae84fb1c4df608370ad9bd572901f7b
2023-12-26 19:26:24 -05:00
Tim Hockin 647c1ef58e Fix go-to-protobuf wrt gengo/v2
There's some very fishy-smelling logic in here, but this commit is
trying to be as focused as possible.

The *.pb.go diffs are the "name" encoded in the descriptor.  The
descriptor blobs can be decoded by this program (thanks StackOverflow!):

```
package main

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"os"

	"io/ioutil"

	proto "github.com/golang/protobuf/proto"
	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
)

func main() {
	m := map[string][]byte{
		"before": blobv1,
		"after":  blobv2,
	}
	arg := os.Args[1]
	dump(m[arg])
}

func dump(bytes []byte) {
	fd, err := decodeFileDesc(bytes)
	if err != nil {
		panic(err)
	}
	b, err := json.MarshalIndent(fd, "", "  ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

// decompress does gzip decompression.
func decompress(b []byte) ([]byte, error) {
	r, err := gzip.NewReader(bytes.NewReader(b))
	if err != nil {
		return nil, fmt.Errorf("bad gzipped descriptor: %v", err)
	}
	out, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, fmt.Errorf("bad gzipped descriptor: %v", err)
	}
	return out, nil
}

func decodeFileDesc(enc []byte) (*dpb.FileDescriptorProto, error) {
	raw, err := decompress(enc)
	if err != nil {
		return nil, fmt.Errorf("failed to decompress enc: %v", err)
	}

	fd := new(dpb.FileDescriptorProto)
	if err := proto.Unmarshal(raw, fd); err != nil {
		return nil, fmt.Errorf("bad descriptor: %v", err)
	}
	return fd, nil
}

var blobv1 = []byte{
	// insert proto "before" blob here
}

var blobv2 = []byte{
	// insert proto "after" blob here
}
```

Running this with "before" and "after" args, and diffing the output
yields something like:

```diff
--- /tmp/a	2023-12-23 23:57:04.748090836 -0800
+++ /tmp/b	2023-12-23 23:57:11.000040973 -0800
@@ -1,5 +1,5 @@
 {
-  "name": "k8s.io/kubernetes/vendor/k8s.io/api/admission/v1/generated.proto",
+  "name": "k8s.io/api/admission/v1/generated.proto",
   "package": "k8s.io.api.admission.v1",
   "dependency": [
     "github.com/gogo/protobuf/gogoproto/gogo.proto",
```

Kubernetes-commit: b0a70dec4ab4cb9f972cf39a81ca5e5555417227
2023-12-24 10:01:42 -08:00
Tim Hockin 657a797b80 Re-vendor latest kube-openapi and gengo/v2
./hack/pin-dependency.sh k8s.io/kube-openapi latest
./hack/pin-dependency.sh k8s.io/gengo/v2 latest
./hack/update-vendor.sh

Kubernetes-commit: 6f2f3735e04df5e4822176a2784069634c3c74a3
2024-02-26 17:02:22 -08:00
Kubernetes Publisher 60eaa65334 Merge pull request #122412 from MadhavJivrajani/bump-go-tools
[go1.22] .*: bump golang.org/x/tools to v0.16.1

Kubernetes-commit: 8a4403a9e5127d2ec3f596c4ce75663e5392cb18
2023-12-20 17:17:33 +00:00
Madhav Jivrajani becf6e96a5 .*: bump golang.org/x/tools to v0.16.1
Bumping tools to include the fix for a nil pointer
deref error in go/types. See golang/go#64812
for more details.

This fix is needed for when we bump to go1.22.

Signed-off-by: Madhav Jivrajani <madhav.jiv@gmail.com>

Kubernetes-commit: a8da4202c0ac785d57b545e6e310fd754888b50e
2023-12-20 14:31:31 +05:30
Kubernetes Publisher 8bd2c20b53 Merge pull request #122395 from pohly/ginkgo-gomega-update
dependencies: gomega v1.30.0 + ginkgo v2.13.2

Kubernetes-commit: 7897910469aa091ebf6576740d055a7137fa147c
2023-12-20 09:15:26 +00:00
Patrick Ohly 4308c9f1f5 dependencies: gomega v1.30.0 + ginkgo v2.13.2
The new gomega.BeTrueBecause and gomega.BeFalseBecause are going to be useful
for https://github.com/kubernetes/kubernetes/issues/105678.

Kubernetes-commit: c8f9ebfb72b6569b4e2ec9733f6998afc6602135
2023-12-19 16:16:02 +01:00
Kubernetes Publisher e2f405af78 Merge pull request #121846 from Iceber/sets_keyset
Set the initial length of set[T] in sets.KeySet

Kubernetes-commit: b0ccc04e4779fe86ab97f10fca36eeb1dd83d344
2023-12-14 01:14:57 +00:00
Kubernetes Publisher 2341c262cb Merge pull request #121771 from pohly/apimachinery-encoding-shortcut
encoding: avoid setting GVK unnecessarily

Kubernetes-commit: 508e3b94fe48136c2c7bff6239986851f0e21dc0
2023-12-14 01:14:57 +00:00
Kubernetes Publisher aa9a6c8873 Merge pull request #121759 from thockin/fix_api_violations
Fix "list_type_missing" API violations in meta/v1

Kubernetes-commit: 8a22571ebb7628b2dd2c2faf453293040b7efaef
2023-12-14 01:14:57 +00:00
Kubernetes Publisher e6fb254b76 Merge pull request #121741 from xgp01/update-util-sets-with-package-cmp
update util/sets to use standard package cmp

Kubernetes-commit: cbe5b1e107feedf7bc7c37a2f506e8782a08302c
2023-12-14 01:14:56 +00:00
Stephen Kitt b64363eaae Use Go 1.21 Ordered and clear for sets
Go 1.21 provides its own type constraint permitting ordered types,
cmp.Ordered; use that instead of the private ordered constraint.

Go 1.21 introduces a clear function for maps, use that to clear sets.

Signed-off-by: Stephen Kitt <skitt@redhat.com>

Kubernetes-commit: 969668b232e4d9b54919a829a1f2d35964a19672
2023-11-23 17:00:55 +01:00
Patrick Ohly 126f5cee56 apimachinery runtime: support contextual logging
In contrast to the original HandleError and HandleCrash, the new
HandleErrorWithContext and HandleCrashWithContext functions properly do contextual
logging, so if a problem occurs while e.g. dealing with a certain request and
WithValues was used for that request, then the error log entry will also
contain information about it.

The output changes from unstructured to structured, which might be a breaking
change for users who grep for panics. Care was taken to format panics
as similar as possible to the original output.

For errors, a message string gets added. There was none before, which made it
impossible to find all error output coming from HandleError.

Keeping HandleError and HandleCrash around without deprecating while changing
the signature of callbacks is a compromise between not breaking existing code
and not adding too many special cases that need to be supported. There is some
code which uses PanicHandlers or ErrorHandlers, but less than code that uses
the Handle* calls.

In Kubernetes, we want to replace the calls. logcheck warns about them in code
which is supposed to be contextual. The steps towards that are:
- add TODO remarks as reminder (this commit)
- locally remove " TODO(pohly): " to enable the check with `//logcheck:context`,
  merge fixes for linter warnings
- once there are none, remove the TODO to enable the check permanently

Kubernetes-commit: 5a130d2b71e5d70cfff15087f4d521c6b68fb01e
2023-11-20 20:25:00 +01:00
Tim Hockin d9203a2f3d Re-vendor k8s.io/kube-openapi
./hack/pin-dependency.sh k8s.io/kube-openapi 778a5567bc1edaed92a4de9c07f90199c67953fa

./hack/update-vendor.sh

Kubernetes-commit: 1f55357d9937f076f532a2c1aa104593b9f6c49a
2023-11-13 10:59:57 -08:00
Kubernetes Publisher fa98d6eaed Merge pull request #121808 from cpanato/go-update-main
[go] Bump images, dependencies and versions to go 1.21.4

Kubernetes-commit: 6ba7258a0f3f73629560fc30016b2e35c8e7ae9c
2023-11-13 17:11:57 +00:00
Iceber Gu 350f69140f Set the initial length of set[T] in sets.KeySet
Signed-off-by: Iceber Gu <caiwei95@hotmail.com>

Kubernetes-commit: 33aa56097a34e1d92d84d0acb6b47adc9a10d301
2023-11-12 00:08:01 +08:00
cpanato f27e43a293 update go.mod
Signed-off-by: cpanato <ctadeu@gmail.com>

Kubernetes-commit: 9e5b8402bb95eb82541099e77c3a8b0ccd31297f
2023-11-08 08:46:15 -06:00
Patrick Ohly 013c6eb4d4 encoding: avoid setting GVK unnecessarily
Setting the group/version/kind is not necessary when the object already has it.
In that particular case some extra work and the data race when the
same object is used multiple times in parallel can be avoided.

Kubernetes-commit: f0aab8c984d329e22c498a3e6f0fe1db9823d1b7
2023-11-07 08:11:44 +01:00
Tim Hockin bfbbdecc1d Fix "list_type_missing" API violations in meta/v1
This assumes that any such field is atomic, except:
  * OwnerReferences: because it has a `+patchStrategy=merge`, but it
    probably needs a `+listMapKey=...` ?
  * Finalizers: because it hs a `+patchStrategy=merge`, but is a
    primitive type (string).
  * []byte fields, which should not be failing this anyway (fixed
    subsequently).

An alternative approach could be just to turn off the API warnings for
these fields, but it felt more correct to declare the semantics.

Kubernetes-commit: 44060fb1f36e0d7b4f25b1dfcd59f54d20459653
2023-11-06 05:25:56 -08:00
xiegangpeng 8398822ccd update util/sets to use standard package cmp
Kubernetes-commit: c26bb7eb85aeb059c127a478f1c6abc512c0e6ce
2023-11-06 10:59:46 +08:00
Ben Luddy 8a0a7f6772 Deduplicate set expression values in metav1.LabelSelector fuzzer.
Internal versions of ScaleStatus types use metav1.LabelSelector to represent label selectors, while
external versions use the textual representation. During conversion to and from text, match
expressions are sorted by key, and values for set operations "in" and "notin" are sorted and
deduplicated. This loss of order and duplication is detected by roundtrip testing.

The existing fuzz function for metav1.LabelSelector sorts match expressions by key and sorts, but
does not deduplicate, set expression values. That function now also deduplicates set expression
values so that fuzzed metav1.LabelSelectors can faithfully roundtrip through the textual label
selector representation.

Kubernetes-commit: a4c2f78b284f0208a26935a348556e49f48e5f1c
2023-10-23 09:44:21 -04:00
Ben Luddy 48a3f600d7 Update vendoring to take new CBOR library dependency.
Kubernetes-commit: 09a1abda998fc37e2e29a120a82be7c6271656e0
2023-10-17 16:51:52 -04:00
Ben Luddy 9fd4d01270 Add skeleton CBOR package and introduce library dependency.
Kubernetes-commit: ef27338bef90d32d046736f186a549f4841b8b01
2023-10-17 16:50:15 -04:00
Lucy Sweet 6db61283ee Improve precision of Quantity.AsApproximateFloat64
This improves the precision of Quantity.AsApproximateFloat64, by way of example:

decQuantity(7*1024*1024, -1, BinarySI)
Before: 734003.2000000001,
After: 734003.2

Co-Authored-By: Bo Sunesen <7479047+bosunesen@users.noreply.github.com>

Kubernetes-commit: 75ba5a96514ad207481b5da323fdec2d5baa9963
2023-08-11 16:28:31 +02:00
Andrew DeMaria ad648b1bce generate fully qualified type references
Currently type references for non-local names are output as relative
types which is subject to the resolution rules as defined at
https://protobuf.com/docs/language-spec#reference-resolution
This works fine within the k8s.io namespace where no subpackages are
named k8s, but other users of go-to-protobuf likely have k8s in their
package name. This causes conflicts in the search resolution when
executing `go-to-protobuf`:

```
company.example.com/k8s/custom/pkg/apis/custom.k8s.example.com/v1/generated.proto:64:12: "k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta" is resolved to "company.example.com.k8s.custom.pkg.apis.custom.k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta", which is not defined. The innermost scope is searched first in name resolution. Consider using a leading '.'(i.e., ".k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta") to start from the outermost scope.
```

To avoid this we can output fully qualified type references using a
preceding dot (.)

This results in a change for k8s generated.proto files, but the
effect is a noop.

Fixes kubernetes/code-generator#147

Signed-off-by: Andrew DeMaria <ademaria@cloudflare.com>

Kubernetes-commit: 9edf1fc51c56d565348c48f3765cf094518ba7ed
2023-03-20 18:02:14 -06:00
Patrick Ohly 305e3b26cf Generate go.work files
This creates go.work and enables Go Workspaces.  This is a file that
includes info on k/k and all the staging modules.

This depends on go 1.22 and setting FORCE_HOST_GO=true (for kube
scripts, which try to be hermetic).

Make this part of the normal update/verify sequence.

The top-level go.work file contains no replace statements. Instead, the
replace statements in the individual go.mod files are used. For this to
work, replace statements in the individual go.mod files have to be
consistent.

hack/tools has different dependencies and can't be in the main
workspace, so this adds a go.work just for that.  Without this, go tries
to consider all deps in all modules and pick one that works for all.
This is problematic because there are so many of them that it is
difficult to manage.

Likewise for k8s.io/code-generator/examples and
k8s.io/kms/internal/plugins/_mock - add trivial go.work files.

For example k/k depends on an older version of a lib that gloangci-lint
needs (transitively) and it breaks.

This also updates vendor (needed to make go happy), and removes
vendor'ed symlinks.  This breaks a LOT of our build tools, which will be
fixed subsequently.

Result: `go` commands work across modules:

Before:
```
$ go list ./pkg/proxy/iptables/ ./staging/src/k8s.io/api/core/v1/
main module (k8s.io/kubernetes) does not contain package k8s.io/kubernetes/staging/src/k8s.io/api/core/v1

$ go build ./pkg/proxy/iptables/ ./staging/src/k8s.io/api
main module (k8s.io/kubernetes) does not contain package k8s.io/kubernetes/staging/src/k8s.io/api

$ go test ./pkg/proxy/iptables/ ./staging/src/k8s.io/api
main module (k8s.io/kubernetes) does not contain package k8s.io/kubernetes/staging/src/k8s.io/api
```

After:
```
$ go list ./pkg/proxy/iptables/ ./staging/src/k8s.io/api/core/v1/
k8s.io/kubernetes/pkg/proxy/iptables
k8s.io/api/core/v1

$ go build ./pkg/proxy/iptables/ ./staging/src/k8s.io/api

$ go test ./pkg/proxy/iptables/ ./staging/src/k8s.io/api
ok  	k8s.io/kubernetes/pkg/proxy/iptables	0.360s
ok  	k8s.io/api	2.302s
```

Result: `make` fails:

```
$ make
go version go1.22rc1 linux/amd64
+++ [0106 12:11:03] Building go targets for linux/amd64
    k8s.io/kubernetes/cmd/kube-proxy (static)
    k8s.io/kubernetes/cmd/kube-apiserver (static)
    k8s.io/kubernetes/cmd/kube-controller-manager (static)
    k8s.io/kubernetes/cmd/kubelet (non-static)
    k8s.io/kubernetes/cmd/kubeadm (static)
    k8s.io/kubernetes/cmd/kube-scheduler (static)
    k8s.io/component-base/logs/kube-log-runner (static)
    k8s.io/kube-aggregator (static)
    k8s.io/apiextensions-apiserver (static)
    k8s.io/kubernetes/cluster/gce/gci/mounter (static)
    k8s.io/kubernetes/cmd/kubectl (static)
    k8s.io/kubernetes/cmd/kubectl-convert (static)
    github.com/onsi/ginkgo/v2/ginkgo (non-static)
    k8s.io/kubernetes/test/e2e/e2e.test (test)
    k8s.io/kubernetes/test/conformance/image/go-runner (non-static)
    k8s.io/kubernetes/cmd/kubemark (static)
    github.com/onsi/ginkgo/v2/ginkgo (non-static)
    k8s.io/kubernetes/test/e2e_node/e2e_node.test (test)
test/e2e/e2e.go:35:2: cannot find package "k8s.io/api/apps/v1" in any of:
	/home/thockin/src/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/k8s.io/api/apps/v1 (vendor tree)
	/home/thockin/src/kubernetes/_output/local/.gimme/versions/go1.22rc1.linux.amd64/src/k8s.io/api/apps/v1 (from $GOROOT)
	/home/thockin/src/kubernetes/_output/local/go/src/k8s.io/api/apps/v1 (from $GOPATH)
	... more ...
	... more ...
	... more ...
!!! [0106 12:13:41] Call tree:
!!! [0106 12:13:41]  1: /home/thockin/src/kubernetes/hack/lib/golang.sh:948 kube::golang::build_binaries_for_platform(...)
!!! [0106 12:13:41]  2: hack/make-rules/build.sh:27 kube::golang::build_binaries(...)
!!! [0106 12:13:41] Call tree:
!!! [0106 12:13:41]  1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...)
!!! [0106 12:13:41] Call tree:
!!! [0106 12:13:41]  1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...)
make: *** [Makefile:96: all] Error 1
```

Again, this requires go 1.22 (e.g. gotip), as go 1.21.x does not have
`go work vendor` support.

TO REPEAT:
    ( \
      ./hack/update-go-workspace.sh; \
      ./hack/update-vendor.sh; \
      ./hack/update-go-workspace.sh; \
    )

Kubernetes-commit: 65b841c077e0d3282d28b9199aec72d23d045104
2022-06-08 12:12:42 +02:00
266 changed files with 21110 additions and 1720 deletions

3
OWNERS
View File

@ -19,10 +19,11 @@ reviewers:
- mikedanese
- liggitt
- sttts
- ncdc
- logicalhan
- jpbetz
labels:
- sig/api-machinery
emeritus_approvers:
- lavalamp
emeritus_reviewers:
- ncdc

2
doc.go
View File

@ -14,4 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package apimachinery // import "k8s.io/apimachinery"
package apimachinery

67
go.mod
View File

@ -2,55 +2,54 @@
module k8s.io/apimachinery
go 1.21.3
go 1.24.0
godebug default=go1.24
require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/davecgh/go-spew v1.1.1
github.com/evanphx/json-patch v4.12.0+incompatible
github.com/fxamacker/cbor/v2 v2.9.0
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.5.3
github.com/google/gnostic-models v0.6.8
github.com/google/go-cmp v0.6.0
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.3.0
github.com/moby/spdystream v0.2.0
github.com/google/gnostic-models v0.7.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/moby/spdystream v0.5.0
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
github.com/onsi/ginkgo/v2 v2.13.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.17.0
golang.org/x/time v0.3.0
github.com/pmezard/go-difflib v1.0.0
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
golang.org/x/net v0.38.0
golang.org/x/time v0.9.0
gopkg.in/evanphx/json-patch.v4 v4.12.0
gopkg.in/inf.v0 v0.9.1
k8s.io/klog/v2 v2.110.1
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
sigs.k8s.io/yaml v1.3.0
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8
sigs.k8s.io/randfill v1.0.0
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
sigs.k8s.io/yaml v1.6.0
)
require (
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/gomega v1.29.0 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/onsi/ginkgo/v2 v2.21.0 // indirect
github.com/onsi/gomega v1.35.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.12.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

142
go.sum
View File

@ -1,44 +1,34 @@
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@ -54,103 +44,103 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
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-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
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/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@ -0,0 +1,54 @@
/*
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 apitesting
import (
"io"
"testing"
)
// Close and fail the test if it returns an error.
func Close(t TestingT, c io.Closer) {
t.Helper()
assertNoError(t, c.Close())
}
// CloseNoOp does nothing. Use as a replacement for Close when you
// need to disable a defer.
func CloseNoOp(TestingT, io.Closer) {}
// TestingT simulates assert.TestingT and assert.tHelper without adding
// testify as a non-test dependency.
type TestingT interface {
Errorf(format string, args ...interface{})
Helper()
}
// Ensure that testing T & B satisfy the TestingT interface
var _ TestingT = &testing.T{}
var _ TestingT = &testing.B{}
// assertNoError simulates assert.NoError without adding testify as a
// non-test dependency.
//
// In test files, use github.com/stretchr/testify/assert instead.
func assertNoError(t TestingT, err error) {
t.Helper()
if err != nil {
t.Errorf("Received unexpected error:\n%+v", err)
}
}

View File

@ -17,19 +17,23 @@ limitations under the License.
package fuzzer
import (
"encoding/json"
"fmt"
"math/rand"
"github.com/google/gofuzz"
"sigs.k8s.io/randfill"
"k8s.io/apimachinery/pkg/runtime"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
kjson "k8s.io/apimachinery/pkg/util/json"
)
// FuzzerFuncs returns a list of func(*SomeType, c fuzz.Continue) functions.
// FuzzerFuncs returns a list of func(*SomeType, c randfill.Continue) functions.
type FuzzerFuncs func(codecs runtimeserializer.CodecFactory) []interface{}
// FuzzerFor can randomly populate api objects that are destined for version.
func FuzzerFor(funcs FuzzerFuncs, src rand.Source, codecs runtimeserializer.CodecFactory) *fuzz.Fuzzer {
f := fuzz.New().NilChance(.5).NumElements(0, 1)
func FuzzerFor(funcs FuzzerFuncs, src rand.Source, codecs runtimeserializer.CodecFactory) *randfill.Filler {
f := randfill.New().NilChance(.5).NumElements(0, 1)
if src != nil {
f.RandSource(src)
}
@ -50,3 +54,20 @@ func MergeFuzzerFuncs(funcs ...FuzzerFuncs) FuzzerFuncs {
return result
})
}
func NormalizeJSONRawExtension(ext *runtime.RawExtension) {
if json.Valid(ext.Raw) {
// RawExtension->JSON encodes struct fields in field index order while map[string]interface{}->JSON encodes
// struct fields (i.e. keys in the map) lexicographically. We have to sort the fields here to ensure the
// JSON in the (RawExtension->)JSON->map[string]interface{}->JSON round trip results in identical JSON.
var u any
err := kjson.Unmarshal(ext.Raw, &u)
if err != nil {
panic(fmt.Sprintf("Failed to encode object: %v", err))
}
ext.Raw, err = kjson.Marshal(&u)
if err != nil {
panic(fmt.Sprintf("Failed to encode object: %v", err))
}
}
}

View File

@ -20,6 +20,7 @@ import (
"bytes"
gojson "encoding/json"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
@ -115,7 +116,7 @@ func (c *CompatibilityTestOptions) Complete(t *testing.T) *CompatibilityTestOpti
c.TestDataDir = "testdata"
}
if c.TestDataDirCurrentVersion == "" {
c.TestDataDirCurrentVersion = filepath.Join(c.TestDataDir, "HEAD")
c.TestDataDirCurrentVersion = filepath.Join(c.TestDataDir, http.MethodHead)
}
if c.TestDataDirsPreviousVersions == nil {
dirs, err := filepath.Glob(filepath.Join(c.TestDataDir, "v*"))
@ -181,10 +182,10 @@ func (c *CompatibilityTestOptions) Complete(t *testing.T) *CompatibilityTestOpti
}
if c.JSON == nil {
c.JSON = json.NewSerializer(json.DefaultMetaFactory, c.Scheme, c.Scheme, true)
c.JSON = json.NewSerializerWithOptions(json.DefaultMetaFactory, c.Scheme, c.Scheme, json.SerializerOptions{Pretty: true})
}
if c.YAML == nil {
c.YAML = json.NewYAMLSerializer(json.DefaultMetaFactory, c.Scheme, c.Scheme)
c.YAML = json.NewSerializerWithOptions(json.DefaultMetaFactory, c.Scheme, c.Scheme, json.SerializerOptions{Yaml: true})
}
if c.Proto == nil {
c.Proto = protobuf.NewSerializer(c.Scheme, c.Scheme)
@ -199,7 +200,7 @@ func (c *CompatibilityTestOptions) Run(t *testing.T) {
for _, gvk := range c.Kinds {
t.Run(makeName(gvk), func(t *testing.T) {
t.Run("HEAD", func(t *testing.T) {
t.Run(http.MethodHead, func(t *testing.T) {
c.runCurrentVersionTest(t, gvk, usedHEADFixtures)
})

View File

@ -24,11 +24,9 @@ import (
"strings"
"testing"
//nolint:staticcheck //iccheck // SA1019 Keep using deprecated module; it still seems to be maintained and the api of the recommended replacement differs
"github.com/golang/protobuf/proto"
"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
flag "github.com/spf13/pflag"
"sigs.k8s.io/randfill"
apitesting "k8s.io/apimachinery/pkg/api/apitesting"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
@ -122,15 +120,15 @@ func GlobalNonRoundTrippableTypes() sets.String {
// RoundTripTypesWithoutProtobuf applies the round-trip test to all round-trippable Kinds
// in the scheme. It will skip all the GroupVersionKinds in the skip list.
func RoundTripTypesWithoutProtobuf(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
func RoundTripTypesWithoutProtobuf(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
roundTripTypes(t, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, true)
}
func RoundTripTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
func RoundTripTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
roundTripTypes(t, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, false)
}
func roundTripTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) {
func roundTripTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) {
for _, group := range groupsFromScheme(scheme) {
t.Logf("starting group %q", group)
internalVersion := schema.GroupVersion{Group: group, Version: runtime.APIVersionInternal}
@ -151,7 +149,7 @@ func roundTripTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimese
// RoundTripExternalTypes applies the round-trip test to all external round-trippable Kinds
// in the scheme. It will skip all the GroupVersionKinds in the nonRoundTripExternalTypes list .
func RoundTripExternalTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
func RoundTripExternalTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
kinds := scheme.AllKnownTypes()
for gvk := range kinds {
if gvk.Version == runtime.APIVersionInternal || globalNonRoundTrippableTypes.Has(gvk.Kind) {
@ -165,7 +163,7 @@ func RoundTripExternalTypes(t *testing.T, scheme *runtime.Scheme, codecFactory r
// RoundTripExternalTypesWithoutProtobuf applies the round-trip test to all external round-trippable Kinds
// in the scheme. It will skip all the GroupVersionKinds in the nonRoundTripExternalTypes list.
func RoundTripExternalTypesWithoutProtobuf(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
func RoundTripExternalTypesWithoutProtobuf(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
kinds := scheme.AllKnownTypes()
for gvk := range kinds {
if gvk.Version == runtime.APIVersionInternal || globalNonRoundTrippableTypes.Has(gvk.Kind) {
@ -177,15 +175,15 @@ func RoundTripExternalTypesWithoutProtobuf(t *testing.T, scheme *runtime.Scheme,
}
}
func RoundTripSpecificKindWithoutProtobuf(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
func RoundTripSpecificKindWithoutProtobuf(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, true)
}
func RoundTripSpecificKind(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
func RoundTripSpecificKind(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) {
roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, false)
}
func roundTripSpecificKind(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) {
func roundTripSpecificKind(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) {
if nonRoundTrippableTypes[gvk] {
t.Logf("skipping %v", gvk)
return
@ -206,8 +204,8 @@ func roundTripSpecificKind(t *testing.T, gvk schema.GroupVersionKind, scheme *ru
// fuzzInternalObject fuzzes an arbitrary runtime object using the appropriate
// fuzzer registered with the apitesting package.
func fuzzInternalObject(t *testing.T, fuzzer *fuzz.Fuzzer, object runtime.Object) runtime.Object {
fuzzer.Fuzz(object)
func fuzzInternalObject(t *testing.T, fuzzer *randfill.Filler, object runtime.Object) runtime.Object {
fuzzer.Fill(object)
j, err := apimeta.TypeAccessor(object)
if err != nil {
@ -227,7 +225,7 @@ func groupsFromScheme(scheme *runtime.Scheme) []string {
return ret.List()
}
func roundTripToAllExternalVersions(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, internalGVK schema.GroupVersionKind, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) {
func roundTripToAllExternalVersions(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, internalGVK schema.GroupVersionKind, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) {
object, err := scheme.New(internalGVK)
if err != nil {
t.Fatalf("Couldn't make a %v? %v", internalGVK, err)
@ -264,7 +262,7 @@ func roundTripToAllExternalVersions(t *testing.T, scheme *runtime.Scheme, codecF
}
}
func roundTripOfExternalType(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, externalGVK schema.GroupVersionKind, skipProtobuf bool) {
func roundTripOfExternalType(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, externalGVK schema.GroupVersionKind, skipProtobuf bool) {
object, err := scheme.New(externalGVK)
if err != nil {
t.Fatalf("Couldn't make a %v? %v", externalGVK, err)
@ -279,7 +277,7 @@ func roundTripOfExternalType(t *testing.T, scheme *runtime.Scheme, codecFactory
typeAcc.SetKind(externalGVK.Kind)
typeAcc.SetAPIVersion(externalGVK.GroupVersion().String())
roundTrip(t, scheme, json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false), object)
roundTrip(t, scheme, json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{}), object)
// TODO remove this hack after we're past the intermediate steps
if !skipProtobuf {
@ -350,14 +348,14 @@ func roundTrip(t *testing.T, scheme *runtime.Scheme, codec runtime.Codec, object
// decode (deserialize) the encoded data back into an object
obj2, err := runtime.Decode(codec, data)
if err != nil {
t.Errorf("%v: %v\nCodec: %#v\nData: %s\nSource: %#v", name, err, codec, dataAsString(data), dump.Pretty(object))
t.Errorf("%v: %v\nCodec: %#v\nData: %s\nSource: %s", name, err, codec, dataAsString(data), dump.Pretty(object))
panic("failed")
}
// ensure that the object produced from decoding the encoded data is equal
// to the original object
if !apiequality.Semantic.DeepEqual(original, obj2) {
t.Errorf("%v: diff: %v\nCodec: %#v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, cmp.Diff(original, obj2), codec, dump.Pretty(original), dataAsString(data), dump.Pretty(obj2))
t.Errorf("%v: diff: %v\nCodec: %#v\nSource:\n\n%s\n\nEncoded:\n\n%s\n\nFinal:\n\n%s", name, cmp.Diff(original, obj2), codec, dump.Pretty(original), dataAsString(data), dump.Pretty(obj2))
return
}
@ -431,7 +429,6 @@ func dataAsString(data []byte) string {
dataString := string(data)
if !strings.HasPrefix(dataString, "{") {
dataString = "\n" + hex.Dump(data)
proto.NewBuffer(make([]byte, 0, 1024)).DebugPrint("decoded object", data)
}
return dataString
}

View File

@ -0,0 +1,247 @@
/*
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 roundtrip
import (
"bytes"
"fmt"
"math/rand"
"os"
"strconv"
"testing"
"time"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
cborserializer "k8s.io/apimachinery/pkg/runtime/serializer/cbor"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/google/go-cmp/cmp"
)
// RoundtripToUnstructured verifies the roundtrip faithfulness of all external types in a scheme
// from native to unstructured and back using both the JSON and CBOR serializers. The intermediate
// unstructured objects produced by both encodings must be identical and be themselves
// roundtrippable to JSON and CBOR.
//
// Values for all external types in the scheme are generated by fuzzing the a value of the
// corresponding internal type and converting it, except for types whose registered GVK appears in
// the "nointernal" set, which are fuzzed directly.
func RoundtripToUnstructured(t *testing.T, scheme *runtime.Scheme, funcs fuzzer.FuzzerFuncs, skipped sets.Set[schema.GroupVersionKind], nointernal sets.Set[schema.GroupVersionKind]) {
codecs := serializer.NewCodecFactory(scheme)
seed := int64(time.Now().Nanosecond())
if override := os.Getenv("TEST_RAND_SEED"); len(override) > 0 {
overrideSeed, err := strconv.ParseInt(override, 10, 64)
if err != nil {
t.Fatal(err)
}
seed = overrideSeed
t.Logf("using overridden seed: %d", seed)
} else {
t.Logf("seed (override with TEST_RAND_SEED if desired): %d", seed)
}
var buf bytes.Buffer
for gvk := range scheme.AllKnownTypes() {
if globalNonRoundTrippableTypes.Has(gvk.Kind) {
continue
}
if gvk.Version == runtime.APIVersionInternal {
continue
}
subtestName := fmt.Sprintf("%s.%s/%s", gvk.Version, gvk.Group, gvk.Kind)
if gvk.Group == "" {
subtestName = fmt.Sprintf("%s/%s", gvk.Version, gvk.Kind)
}
t.Run(subtestName, func(t *testing.T) {
if skipped.Has(gvk) {
t.SkipNow()
}
fuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, funcs), rand.NewSource(seed), codecs)
for i := 0; i < *FuzzIters; i++ {
item, err := scheme.New(gvk)
if err != nil {
t.Fatalf("couldn't create external object %v: %v", gvk.Kind, err)
}
if nointernal.Has(gvk) {
fuzzer.Fill(item)
} else {
internalObj, err := scheme.New(gvk.GroupKind().WithVersion(runtime.APIVersionInternal))
if err != nil {
t.Fatalf("couldn't create internal object %v: %v", gvk.Kind, err)
}
fuzzer.Fill(internalObj)
if err := scheme.Convert(internalObj, item, nil); err != nil {
t.Fatalf("conversion for %v failed: %v", gvk.Kind, err)
}
}
// Decoding into Unstructured requires that apiVersion and kind be
// serialized, so populate TypeMeta.
item.GetObjectKind().SetGroupVersionKind(gvk)
jsonSerializer := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, scheme, scheme, jsonserializer.SerializerOptions{})
cborSerializer := cborserializer.NewSerializer(scheme, scheme)
// original->JSON->Unstructured
buf.Reset()
if err := jsonSerializer.Encode(item, &buf); err != nil {
t.Fatalf("error encoding native to json: %v", err)
}
var uJSON runtime.Object = &unstructured.Unstructured{}
uJSON, _, err = jsonSerializer.Decode(buf.Bytes(), &gvk, uJSON)
if err != nil {
t.Fatalf("error decoding json to unstructured: %v", err)
}
// original->CBOR->Unstructured
buf.Reset()
if err := cborSerializer.Encode(item, &buf); err != nil {
t.Fatalf("error encoding native to cbor: %v", err)
}
var uCBOR runtime.Object = &unstructured.Unstructured{}
uCBOR, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBOR)
if err != nil {
diag, _ := cbor.Diagnose(buf.Bytes())
t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag)
}
// original->JSON->Unstructured == original->CBOR->Unstructured
if !apiequality.Semantic.DeepEqual(uJSON, uCBOR) {
t.Fatalf("unstructured via json differed from unstructured via cbor: %v", cmp.Diff(uJSON, uCBOR))
}
// original->CBOR(nondeterministic)->Unstructured
buf.Reset()
if err := cborSerializer.EncodeNondeterministic(item, &buf); err != nil {
t.Fatalf("error encoding native to cbor: %v", err)
}
var uCBORNondeterministic runtime.Object = &unstructured.Unstructured{}
uCBORNondeterministic, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBORNondeterministic)
if err != nil {
diag, _ := cbor.Diagnose(buf.Bytes())
t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag)
}
// original->CBOR->Unstructured == original->CBOR(nondeterministic)->Unstructured
if !apiequality.Semantic.DeepEqual(uCBOR, uCBORNondeterministic) {
t.Fatalf("unstructured via nondeterministic cbor differed from unstructured via cbor: %v", cmp.Diff(uCBOR, uCBORNondeterministic))
}
// original->JSON/CBOR->Unstructured == original->JSON/CBOR->Unstructured->JSON->Unstructured
buf.Reset()
if err := jsonSerializer.Encode(uJSON, &buf); err != nil {
t.Fatalf("error encoding unstructured to json: %v", err)
}
var uJSON2 runtime.Object = &unstructured.Unstructured{}
uJSON2, _, err = jsonSerializer.Decode(buf.Bytes(), &gvk, uJSON2)
if err != nil {
t.Fatalf("error decoding json to unstructured: %v", err)
}
if !apiequality.Semantic.DeepEqual(uJSON, uJSON2) {
t.Errorf("object changed during native-json-unstructured-json-unstructured roundtrip, diff: %s", cmp.Diff(uJSON, uJSON2))
}
// original->JSON/CBOR->Unstructured == original->JSON/CBOR->Unstructured->CBOR->Unstructured
buf.Reset()
if err := cborSerializer.Encode(uCBOR, &buf); err != nil {
t.Fatalf("error encoding unstructured to cbor: %v", err)
}
var uCBOR2 runtime.Object = &unstructured.Unstructured{}
uCBOR2, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBOR2)
if err != nil {
diag, _ := cbor.Diagnose(buf.Bytes())
t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag)
}
if !apiequality.Semantic.DeepEqual(uCBOR, uCBOR2) {
t.Errorf("object changed during native-cbor-unstructured-cbor-unstructured roundtrip, diff: %s", cmp.Diff(uCBOR, uCBOR2))
}
// original->JSON/CBOR->Unstructured->CBOR->Unstructured == original->JSON/CBOR->Unstructured->CBOR(nondeterministic)->Unstructured
buf.Reset()
if err := cborSerializer.EncodeNondeterministic(uCBOR, &buf); err != nil {
t.Fatalf("error encoding unstructured to cbor: %v", err)
}
var uCBOR2Nondeterministic runtime.Object = &unstructured.Unstructured{}
uCBOR2Nondeterministic, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBOR2Nondeterministic)
if err != nil {
diag, _ := cbor.Diagnose(buf.Bytes())
t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag)
}
if !apiequality.Semantic.DeepEqual(uCBOR, uCBOR2Nondeterministic) {
t.Errorf("object changed during native-cbor-unstructured-cbor(nondeterministic)-unstructured roundtrip, diff: %s", cmp.Diff(uCBOR, uCBOR2Nondeterministic))
}
// original->JSON/CBOR->Unstructured->JSON->final == original
buf.Reset()
if err := jsonSerializer.Encode(uJSON, &buf); err != nil {
t.Fatalf("error encoding unstructured to json: %v", err)
}
finalJSON, _, err := jsonSerializer.Decode(buf.Bytes(), &gvk, nil)
if err != nil {
t.Fatalf("error decoding json to native: %v", err)
}
if !apiequality.Semantic.DeepEqual(item, finalJSON) {
t.Errorf("object changed during native-json-unstructured-json-native roundtrip, diff: %s", cmp.Diff(item, finalJSON))
}
// original->JSON/CBOR->Unstructured->CBOR->final == original
buf.Reset()
if err := cborSerializer.Encode(uCBOR, &buf); err != nil {
t.Fatalf("error encoding unstructured to cbor: %v", err)
}
finalCBOR, _, err := cborSerializer.Decode(buf.Bytes(), &gvk, nil)
if err != nil {
diag, _ := cbor.Diagnose(buf.Bytes())
t.Fatalf("error decoding cbor to native: %v, diag: %s", err, diag)
}
if !apiequality.Semantic.DeepEqual(item, finalCBOR) {
t.Errorf("object changed during native-cbor-unstructured-cbor-native roundtrip, diff: %s", cmp.Diff(item, finalCBOR))
}
// original->JSON/CBOR->Unstructured->CBOR(nondeterministic)->final == original
buf.Reset()
if err := cborSerializer.EncodeNondeterministic(uCBOR, &buf); err != nil {
t.Fatalf("error encoding unstructured to cbor: %v", err)
}
finalCBORNondeterministic, _, err := cborSerializer.Decode(buf.Bytes(), &gvk, nil)
if err != nil {
diag, _ := cbor.Diagnose(buf.Bytes())
t.Fatalf("error decoding cbor to native: %v, diag: %s", err, diag)
}
if !apiequality.Semantic.DeepEqual(item, finalCBORNondeterministic) {
t.Errorf("object changed during native-cbor-unstructured-cbor-native roundtrip, diff: %s", cmp.Diff(item, finalCBORNondeterministic))
}
}
})
}
}

View File

@ -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

View File

@ -54,6 +54,7 @@ var knownReasons = map[metav1.StatusReason]struct{}{
metav1.StatusReasonGone: {},
metav1.StatusReasonInvalid: {},
metav1.StatusReasonServerTimeout: {},
metav1.StatusReasonStoreReadError: {},
metav1.StatusReasonTimeout: {},
metav1.StatusReasonTooManyRequests: {},
metav1.StatusReasonBadRequest: {},
@ -437,7 +438,7 @@ func NewGenericServerResponse(code int, verb string, qualifiedResource schema.Gr
message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
switch code {
case http.StatusConflict:
if verb == "POST" {
if verb == http.MethodPost {
reason = metav1.StatusReasonAlreadyExists
} else {
reason = metav1.StatusReasonConflict
@ -775,6 +776,12 @@ func IsUnexpectedObjectError(err error) bool {
return err != nil && (ok || errors.As(err, &uoe))
}
// IsStoreReadError determines if err is due to either failure to transform the
// data from the storage, or failure to decode the object appropriately.
func IsStoreReadError(err error) bool {
return ReasonForError(err) == metav1.StatusReasonStoreReadError
}
// SuggestsClientDelay returns true if this error suggests a client delay as well as the
// suggested seconds to wait, or false if the error does not imply a wait. It does not
// address whether the error *should* be retried, since some errors (like a 3xx) may

View File

@ -110,13 +110,13 @@ func TestErrorNew(t *testing.T) {
if time, ok := SuggestsClientDelay(NewTooManyRequests("doing something", 1)); time != 1 || !ok {
t.Errorf("unexpected %d", time)
}
if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, "get", resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok {
if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, http.MethodGet, resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok {
t.Errorf("unexpected %d", time)
}
if time, ok := SuggestsClientDelay(NewGenericServerResponse(500, "get", resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok {
if time, ok := SuggestsClientDelay(NewGenericServerResponse(500, http.MethodGet, resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok {
t.Errorf("unexpected %d", time)
}
if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, "get", resource("tests"), "test", "doing something", 0, true)); time != 0 || ok {
if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, http.MethodGet, resource("tests"), "test", "doing something", 0, true)); time != 0 || ok {
t.Errorf("unexpected %d", time)
}
}

View File

@ -10,5 +10,6 @@ reviewers:
- mikedanese
- liggitt
- janetkuo
- ncdc
- dims
emeritus_reviewers:
- ncdc

View File

@ -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

View File

@ -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

View File

@ -25,15 +25,15 @@ import (
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
"sigs.k8s.io/randfill"
)
func TestAsPartialObjectMetadata(t *testing.T) {
f := fuzz.New().NilChance(.5).NumElements(0, 1).RandSource(rand.NewSource(1))
f := randfill.New().NilChance(.5).NumElements(0, 1).RandSource(rand.NewSource(1))
for i := 0; i < 100; i++ {
m := &metav1.ObjectMeta{}
f.Fuzz(m)
f.Fill(m)
partial := AsPartialObjectMetadata(m)
if !reflect.DeepEqual(&partial.ObjectMeta, m) {
t.Fatalf("incomplete partial object metadata: %s", cmp.Diff(&partial.ObjectMeta, m))
@ -42,7 +42,7 @@ func TestAsPartialObjectMetadata(t *testing.T) {
for i := 0; i < 100; i++ {
m := &metav1beta1.PartialObjectMetadata{}
f.Fuzz(&m.ObjectMeta)
f.Fill(&m.ObjectMeta)
partial := AsPartialObjectMetadata(m)
if !reflect.DeepEqual(&partial.ObjectMeta, &m.ObjectMeta) {
t.Fatalf("incomplete partial object metadata: %s", cmp.Diff(&partial.ObjectMeta, &m.ObjectMeta))

View File

@ -0,0 +1,102 @@
/*
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 (
"slices"
"strings"
)
// 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 []string
// Request provides information about the request being validated.
Request Request
}
// HasOption returns true if the given string is in the Options slice.
func (o Operation) HasOption(option string) bool {
return slices.Contains(o.Options, option)
}
// Request provides information about the request being validated.
type Request struct {
// Subresources identifies the subresource path components of the request. For
// example, Subresources for a request to `/api/v1/pods/my-pod/status` would be
// `["status"]`. For `/api/v1/widget/my-widget/x/y/z`, it would be `["x", "y",
// "z"]`. For a root resource (`/api/v1/pods/my-pod`), Subresources will be an
// empty slice.
//
// Validation logic should only consult this field if the validation rules for a
// particular field differ depending on whether the main resource or a specific
// subresource is being accessed. For example:
//
// Updates to a Pod resource (`/`) normally cannot change container resource
// requests/limits after the Pod is created (they are immutable). However, when
// accessing the Pod's "resize" subresource (`/resize`), these specific fields
// are allowed to be modified. In this scenario, the validation logic for
// `spec.container[*].resources` must check `Subresources` to permit changes only
// when the request targets the "resize" subresource.
//
// Note: This field should not be used to control which fields a subresource
// operation is allowed to write. This is the responsibility of "field wiping".
// Field wiping logic is expected to be handled in resource strategies by
// modifying the incoming object before it is validated.
Subresources []string
}
// SubresourcePath returns the path is a slash-separated list of subresource
// names. For example, `/status`, `/resize`, or `/x/y/z`.
func (r Request) SubresourcePath() string {
if len(r.Subresources) == 0 {
return "/"
}
return "/" + strings.Join(r.Subresources, "/")
}
// 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
)

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/api/resource/generated.proto
// source: k8s.io/apimachinery/pkg/api/resource/generated.proto
package resource
@ -41,7 +41,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
func (m *Quantity) Reset() { *m = Quantity{} }
func (*Quantity) ProtoMessage() {}
func (*Quantity) Descriptor() ([]byte, []int) {
return fileDescriptor_612bba87bd70906c, []int{0}
return fileDescriptor_7288c78ff45111e9, []int{0}
}
func (m *Quantity) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Quantity.Unmarshal(m, b)
@ -64,7 +64,7 @@ var xxx_messageInfo_Quantity proto.InternalMessageInfo
func (m *QuantityValue) Reset() { *m = QuantityValue{} }
func (*QuantityValue) ProtoMessage() {}
func (*QuantityValue) Descriptor() ([]byte, []int) {
return fileDescriptor_612bba87bd70906c, []int{1}
return fileDescriptor_7288c78ff45111e9, []int{1}
}
func (m *QuantityValue) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QuantityValue.Unmarshal(m, b)
@ -90,25 +90,24 @@ func init() {
}
func init() {
proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/api/resource/generated.proto", fileDescriptor_612bba87bd70906c)
proto.RegisterFile("k8s.io/apimachinery/pkg/api/resource/generated.proto", fileDescriptor_7288c78ff45111e9)
}
var fileDescriptor_612bba87bd70906c = []byte{
// 254 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xf2, 0xcd, 0xb6, 0x28, 0xd6,
0xcb, 0xcc, 0xd7, 0xcf, 0x2e, 0x4d, 0x4a, 0x2d, 0xca, 0x4b, 0x2d, 0x49, 0x2d, 0xd6, 0x2f, 0x4b,
0xcd, 0x4b, 0xc9, 0x2f, 0xd2, 0x87, 0x4a, 0x24, 0x16, 0x64, 0xe6, 0x26, 0x26, 0x67, 0x64, 0xe6,
0xa5, 0x16, 0x55, 0xea, 0x17, 0x64, 0xa7, 0x83, 0x04, 0xf4, 0x8b, 0x52, 0x8b, 0xf3, 0x4b, 0x8b,
0x92, 0x53, 0xf5, 0xd3, 0x53, 0xf3, 0x52, 0x8b, 0x12, 0x4b, 0x52, 0x53, 0xf4, 0x0a, 0x8a, 0xf2,
0x4b, 0xf2, 0x85, 0x54, 0x20, 0xba, 0xf4, 0x90, 0x75, 0xe9, 0x15, 0x64, 0xa7, 0x83, 0x04, 0xf4,
0x60, 0xba, 0xa4, 0x74, 0xd3, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0xd3,
0xf3, 0xd3, 0xf3, 0xf5, 0xc1, 0x9a, 0x93, 0x4a, 0xd3, 0xc0, 0x3c, 0x30, 0x07, 0xcc, 0x82, 0x18,
0xaa, 0x64, 0xc1, 0xc5, 0x11, 0x58, 0x9a, 0x98, 0x57, 0x92, 0x59, 0x52, 0x29, 0x24, 0xc6, 0xc5,
0x56, 0x5c, 0x52, 0x94, 0x99, 0x97, 0x2e, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x19, 0x04, 0xe5, 0x59,
0x89, 0xcc, 0x58, 0x20, 0xcf, 0xd0, 0xb1, 0x50, 0x9e, 0x61, 0xc2, 0x42, 0x79, 0x86, 0x05, 0x0b,
0xe5, 0x19, 0x1a, 0xee, 0x28, 0x30, 0x28, 0xd9, 0x72, 0xf1, 0xc2, 0x74, 0x86, 0x25, 0xe6, 0x94,
0xa6, 0x92, 0xa6, 0xdd, 0xc9, 0xeb, 0xc4, 0x43, 0x39, 0x86, 0x0b, 0x0f, 0xe5, 0x18, 0x6e, 0x3c,
0x94, 0x63, 0x68, 0x78, 0x24, 0xc7, 0x78, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x37,
0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0x43, 0x94, 0x0a, 0x31, 0x21,
0x05, 0x08, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x70, 0x98, 0xa3, 0x69, 0x01, 0x00, 0x00,
var fileDescriptor_7288c78ff45111e9 = []byte{
// 234 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0xc9, 0xb6, 0x28, 0xd6,
0xcb, 0xcc, 0xd7, 0x4f, 0x2c, 0xc8, 0xcc, 0x4d, 0x4c, 0xce, 0xc8, 0xcc, 0x4b, 0x2d, 0xaa, 0xd4,
0x2f, 0xc8, 0x4e, 0x07, 0x09, 0xe8, 0x17, 0xa5, 0x16, 0xe7, 0x97, 0x16, 0x25, 0xa7, 0xea, 0xa7,
0xa7, 0xe6, 0xa5, 0x16, 0x25, 0x96, 0xa4, 0xa6, 0xe8, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0xa9,
0x40, 0x74, 0xe9, 0x21, 0xeb, 0xd2, 0x2b, 0xc8, 0x4e, 0x07, 0x09, 0xe8, 0xc1, 0x74, 0x49, 0xe9,
0xa6, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xa7, 0xe7, 0xa7, 0xe7, 0xeb,
0x83, 0x35, 0x27, 0x95, 0xa6, 0x81, 0x79, 0x60, 0x0e, 0x98, 0x05, 0x31, 0x54, 0xc9, 0x82, 0x8b,
0x23, 0xb0, 0x34, 0x31, 0xaf, 0x24, 0xb3, 0xa4, 0x52, 0x48, 0x8c, 0x8b, 0xad, 0xb8, 0xa4, 0x28,
0x33, 0x2f, 0x5d, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x08, 0xca, 0xb3, 0x12, 0x99, 0xb1, 0x40,
0x9e, 0xa1, 0x63, 0xa1, 0x3c, 0xc3, 0x84, 0x85, 0xf2, 0x0c, 0x0b, 0x16, 0xca, 0x33, 0x34, 0xdc,
0x51, 0x60, 0x50, 0xb2, 0xe5, 0xe2, 0x85, 0xe9, 0x0c, 0x4b, 0xcc, 0x29, 0x4d, 0x25, 0x4d, 0xbb,
0x93, 0xd7, 0x89, 0x87, 0x72, 0x0c, 0x17, 0x1e, 0xca, 0x31, 0xdc, 0x78, 0x28, 0xc7, 0xd0, 0xf0,
0x48, 0x8e, 0xf1, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x6f, 0x3c, 0x92, 0x63, 0x7c,
0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x28, 0x15, 0x62, 0x42, 0x0a, 0x10, 0x00, 0x00,
0xff, 0xff, 0x50, 0x91, 0xd0, 0x9c, 0x50, 0x01, 0x00, 0x00,
}

View File

@ -20,11 +20,13 @@ import (
"bytes"
"errors"
"fmt"
"math"
math "math"
"math/big"
"strconv"
"strings"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
inf "gopkg.in/inf.v0"
)
@ -458,9 +460,10 @@ func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) {
}
}
// AsApproximateFloat64 returns a float64 representation of the quantity which may
// lose precision. If the value of the quantity is outside the range of a float64
// +Inf/-Inf will be returned.
// AsApproximateFloat64 returns a float64 representation of the quantity which
// may lose precision. If precision matter more than performance, see
// AsFloat64Slow. If the value of the quantity is outside the range of a
// float64 +Inf/-Inf will be returned.
func (q *Quantity) AsApproximateFloat64() float64 {
var base float64
var exponent int
@ -478,6 +481,36 @@ func (q *Quantity) AsApproximateFloat64() float64 {
return base * math.Pow10(exponent)
}
// AsFloat64Slow returns a float64 representation of the quantity. This is
// more precise than AsApproximateFloat64 but significantly slower. If the
// value of the quantity is outside the range of a float64 +Inf/-Inf will be
// returned.
func (q *Quantity) AsFloat64Slow() float64 {
infDec := q.AsDec()
var absScale int64
if infDec.Scale() < 0 {
absScale = int64(-infDec.Scale())
} else {
absScale = int64(infDec.Scale())
}
pow10AbsScale := big.NewInt(10)
pow10AbsScale = pow10AbsScale.Exp(pow10AbsScale, big.NewInt(absScale), nil)
var resultBigFloat *big.Float
if infDec.Scale() < 0 {
resultBigInt := new(big.Int).Mul(infDec.UnscaledBig(), pow10AbsScale)
resultBigFloat = new(big.Float).SetInt(resultBigInt)
} else {
pow10AbsScaleFloat := new(big.Float).SetInt(pow10AbsScale)
resultBigFloat = new(big.Float).SetInt(infDec.UnscaledBig())
resultBigFloat = resultBigFloat.Quo(resultBigFloat, pow10AbsScaleFloat)
}
result, _ := resultBigFloat.Float64()
return result
}
// AsInt64 returns a representation of the current value as an int64 if a fast conversion
// is possible. If false is returned, callers must use the inf.Dec form of this quantity.
func (q *Quantity) AsInt64() (int64, bool) {
@ -683,6 +716,12 @@ func (q Quantity) MarshalJSON() ([]byte, error) {
return result, nil
}
func (q Quantity) MarshalCBOR() ([]byte, error) {
// The call to String() should never return the string "<nil>" because the receiver's
// address will never be nil.
return cbor.Marshal(q.String())
}
// ToUnstructured implements the value.UnstructuredConverter interface.
func (q Quantity) ToUnstructured() interface{} {
return q.String()
@ -711,6 +750,27 @@ func (q *Quantity) UnmarshalJSON(value []byte) error {
return nil
}
func (q *Quantity) UnmarshalCBOR(value []byte) error {
var s *string
if err := cbor.Unmarshal(value, &s); err != nil {
return err
}
if s == nil {
q.d.Dec = nil
q.i = int64Amount{}
return nil
}
parsed, err := ParseQuantity(strings.TrimSpace(*s))
if err != nil {
return err
}
*q = parsed
return nil
}
// NewDecimalQuantity returns a new Quantity representing the given
// value in the given format.
func NewDecimalQuantity(b inf.Dec, format Format) *Quantity {

View File

@ -27,10 +27,12 @@ import (
"testing"
"unicode"
fuzz "github.com/google/gofuzz"
"github.com/google/go-cmp/cmp"
"github.com/spf13/pflag"
inf "gopkg.in/inf.v0"
"sigs.k8s.io/randfill"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
)
var (
@ -825,12 +827,12 @@ func TestQuantityParseEmit(t *testing.T) {
}
}
var fuzzer = fuzz.New().Funcs(
func(q *Quantity, c fuzz.Continue) {
var fuzzer = randfill.New().Funcs(
func(q *Quantity, c randfill.Continue) {
q.i = Zero
if c.RandBool() {
if c.Bool() {
q.Format = BinarySI
if c.RandBool() {
if c.Bool() {
dec := &inf.Dec{}
q.d = infDecAmount{Dec: dec}
dec.SetScale(0)
@ -844,12 +846,12 @@ var fuzzer = fuzz.New().Funcs(
dec.SetUnscaled(c.Int63n(1024) << uint(10*c.Intn(5)))
return
}
if c.RandBool() {
if c.Bool() {
q.Format = DecimalSI
} else {
q.Format = DecimalExponent
}
if c.RandBool() {
if c.Bool() {
dec := &inf.Dec{}
q.d = infDecAmount{Dec: dec}
dec.SetScale(inf.Scale(c.Intn(4)))
@ -895,7 +897,7 @@ func TestQuantityDeepCopy(t *testing.T) {
func TestJSON(t *testing.T) {
for i := 0; i < 500; i++ {
q := &Quantity{}
fuzzer.Fuzz(q)
fuzzer.Fill(q)
b, err := json.Marshal(q)
if err != nil {
t.Errorf("error encoding %v: %v", q, err)
@ -1292,6 +1294,7 @@ func TestNegateRoundTrip(t *testing.T) {
}
func TestQuantityAsApproximateFloat64(t *testing.T) {
// NOTE: this table should be kept in sync with TestQuantityAsFloat64Slow
table := []struct {
in Quantity
out float64
@ -1342,11 +1345,11 @@ func TestQuantityAsApproximateFloat64(t *testing.T) {
{decQuantity(-12, 500, DecimalSI), math.Inf(-1)},
}
for _, item := range table {
for i, item := range table {
t.Run(fmt.Sprintf("%s %s", item.in.Format, item.in.String()), func(t *testing.T) {
out := item.in.AsApproximateFloat64()
if out != item.out {
t.Fatalf("expected %v, got %v", item.out, out)
t.Fatalf("test %d expected %v, got %v", i+1, item.out, out)
}
if item.in.d.Dec != nil {
if i, ok := item.in.AsInt64(); ok {
@ -1361,6 +1364,77 @@ func TestQuantityAsApproximateFloat64(t *testing.T) {
}
}
func TestQuantityAsFloat64Slow(t *testing.T) {
// NOTE: this table should be kept in sync with TestQuantityAsApproximateFloat64
table := []struct {
in Quantity
out float64
}{
{decQuantity(0, 0, DecimalSI), 0.0},
{decQuantity(0, 0, DecimalExponent), 0.0},
{decQuantity(0, 0, BinarySI), 0.0},
{decQuantity(1, 0, DecimalSI), 1},
{decQuantity(1, 0, DecimalExponent), 1},
{decQuantity(1, 0, BinarySI), 1},
// Binary suffixes
{decQuantity(1024, 0, BinarySI), 1024},
{decQuantity(8*1024, 0, BinarySI), 8 * 1024},
{decQuantity(7*1024*1024, 0, BinarySI), 7 * 1024 * 1024},
{decQuantity(7*1024*1024, 1, BinarySI), (7 * 1024 * 1024) * 10},
{decQuantity(7*1024*1024, 4, BinarySI), (7 * 1024 * 1024) * 10000},
{decQuantity(7*1024*1024, 8, BinarySI), (7 * 1024 * 1024) * 100000000},
{decQuantity(7*1024*1024, -1, BinarySI), (7 * 1024 * 1024) / float64(10)},
{decQuantity(7*1024*1024, -8, BinarySI), (7 * 1024 * 1024) / float64(100000000)},
{decQuantity(1024, 0, DecimalSI), 1024},
{decQuantity(8*1024, 0, DecimalSI), 8 * 1024},
{decQuantity(7*1024*1024, 0, DecimalSI), 7 * 1024 * 1024},
{decQuantity(7*1024*1024, 1, DecimalSI), (7 * 1024 * 1024) * 10},
{decQuantity(7*1024*1024, 4, DecimalSI), (7 * 1024 * 1024) * 10000},
{decQuantity(7*1024*1024, 8, DecimalSI), (7 * 1024 * 1024) * 100000000},
{decQuantity(7*1024*1024, -1, DecimalSI), (7 * 1024 * 1024) / float64(10)},
{decQuantity(7*1024*1024, -8, DecimalSI), (7 * 1024 * 1024) / float64(100000000)},
{decQuantity(1024, 0, DecimalExponent), 1024},
{decQuantity(8*1024, 0, DecimalExponent), 8 * 1024},
{decQuantity(7*1024*1024, 0, DecimalExponent), 7 * 1024 * 1024},
{decQuantity(7*1024*1024, 1, DecimalExponent), (7 * 1024 * 1024) * 10},
{decQuantity(7*1024*1024, 4, DecimalExponent), (7 * 1024 * 1024) * 10000},
{decQuantity(7*1024*1024, 8, DecimalExponent), (7 * 1024 * 1024) * 100000000},
{decQuantity(7*1024*1024, -1, DecimalExponent), (7 * 1024 * 1024) / float64(10)},
{decQuantity(7*1024*1024, -8, DecimalExponent), (7 * 1024 * 1024) / float64(100000000)},
// very large numbers
{Quantity{d: maxAllowed, Format: DecimalSI}, math.MaxInt64},
{Quantity{d: maxAllowed, Format: BinarySI}, math.MaxInt64},
{decQuantity(12, 18, DecimalSI), 1.2e19},
// infinities caused due to float64 overflow
{decQuantity(12, 500, DecimalSI), math.Inf(0)},
{decQuantity(-12, 500, DecimalSI), math.Inf(-1)},
}
for i, item := range table {
t.Run(fmt.Sprintf("%s %s", item.in.Format, item.in.String()), func(t *testing.T) {
out := item.in.AsFloat64Slow()
if out != item.out {
t.Fatalf("test %d expected %v, got %v", i+1, item.out, out)
}
if item.in.d.Dec != nil {
if i, ok := item.in.AsInt64(); ok {
q := intQuantity(i, 0, item.in.Format)
out := q.AsFloat64Slow()
if out != item.out {
t.Fatalf("as int quantity: expected %v, got %v", item.out, out)
}
}
}
})
}
}
func TestStringQuantityAsApproximateFloat64(t *testing.T) {
table := []struct {
in string
@ -1395,6 +1469,40 @@ func TestStringQuantityAsApproximateFloat64(t *testing.T) {
}
}
func TestStringQuantityAsFloat64Slow(t *testing.T) {
table := []struct {
in string
out float64
}{
{"2Ki", 2048},
{"1.1Ki", 1126.4e+0},
{"1Mi", 1.048576e+06},
{"2Gi", 2.147483648e+09},
}
for _, item := range table {
t.Run(item.in, func(t *testing.T) {
in, err := ParseQuantity(item.in)
if err != nil {
t.Fatal(err)
}
out := in.AsFloat64Slow()
if out != item.out {
t.Fatalf("expected %v, got %v", item.out, out)
}
if in.d.Dec != nil {
if i, ok := in.AsInt64(); ok {
q := intQuantity(i, 0, in.Format)
out := q.AsFloat64Slow()
if out != item.out {
t.Fatalf("as int quantity: expected %v, got %v", item.out, out)
}
}
}
})
}
}
func benchmarkQuantities() []Quantity {
return []Quantity{
intQuantity(1024*1024*1024, 0, BinarySI),
@ -1577,6 +1685,18 @@ func BenchmarkQuantityAsApproximateFloat64(b *testing.B) {
b.StopTimer()
}
func BenchmarkQuantityAsFloat64Slow(b *testing.B) {
values := benchmarkQuantities()
b.ResetTimer()
for i := 0; i < b.N; i++ {
q := values[i%len(values)]
if q.AsFloat64Slow() == -1 {
b.Fatal(q)
}
}
b.StopTimer()
}
var _ pflag.Value = &QuantityValue{}
func TestQuantityValueSet(t *testing.T) {
@ -1615,3 +1735,88 @@ func ExampleQuantityValue() {
// Output:
// --mem quantity sets amount of memory (default 1Mi)
}
func TestQuantityUnmarshalCBOR(t *testing.T) {
for _, tc := range []struct {
name string
in []byte
want Quantity
errMessage string
}{
{
name: "null",
in: []byte{0xf6}, // null
want: Quantity{},
},
{
name: "text string input",
in: []byte("\x621M"), // "1M"
want: Quantity{i: int64Amount{value: 1, scale: 6}},
},
{
name: "byte string input",
in: []byte("\x421M"), // '1M'
want: Quantity{i: int64Amount{value: 1, scale: 6}},
},
{
name: "whitespace",
in: []byte("\x4a \t\n\r1M \t\n\r"), // h'20090a0d314d20090a0d'
want: Quantity{i: int64Amount{value: 1, scale: 6}},
},
{
name: "empty byte string",
in: []byte{0x40},
errMessage: ErrFormatWrong.Error(),
},
{
name: "empty text string",
in: []byte{0x60},
errMessage: ErrFormatWrong.Error(),
},
{
name: "unsupported input type",
in: []byte{0x07}, // 7
errMessage: "cbor: cannot unmarshal positive integer into Go value of type string",
},
} {
t.Run(tc.name, func(t *testing.T) {
var got Quantity
if err := got.UnmarshalCBOR(tc.in); err != nil {
if tc.errMessage == "" {
t.Fatalf("want nil error, got: %v", err)
} else if gotMessage := err.Error(); tc.errMessage != gotMessage {
t.Fatalf("want error: %q, got: %q", tc.errMessage, gotMessage)
}
} else if tc.errMessage != "" {
t.Fatalf("got nil error, want: %s", tc.errMessage)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("unexpected diff:\n%s", diff)
}
})
}
}
func TestQuantityRoundtripCBOR(t *testing.T) {
for i := 0; i < 500; i++ {
var initial, final Quantity
fuzzer.Fill(&initial)
b, err := cbor.Marshal(initial)
if err != nil {
t.Errorf("error encoding %v: %v", initial, err)
continue
}
err = cbor.Unmarshal(b, &final)
if err != nil {
t.Errorf("%v: error decoding %v: %v", initial, string(b), err)
}
if final.Cmp(initial) != 0 {
diag, err := cbor.Diagnose(b)
if err != nil {
t.Logf("failed to produce diagnostic encoding of 0x%x: %v", b, err)
}
t.Errorf("Expected equal: %v, %v (cbor was '%s')", initial, final, diag)
}
}
}

59
pkg/api/safe/safe.go Normal file
View File

@ -0,0 +1,59 @@
/*
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 safe
// Field takes a pointer to any value (which may or may not be nil) and a
// function that traverses to a target type R (a typical use case is to
// dereference a field), and returns the result of the traversal, or the zero
// value of the target type.
//
// This is roughly equivalent to:
//
// value != nil ? fn(value) : zero-value
//
// ...in languages that support the ternary operator.
func Field[V any, R any](value *V, fn func(*V) R) R {
if value == nil {
var zero R
return zero
}
o := fn(value)
return o
}
// Cast takes any value, attempts to cast it to T, and returns the T value if
// the cast is successful, or else the zero value of T.
func Cast[T any](value any) T {
result, _ := value.(T)
return result
}
// Value takes a pointer to any value (which may or may not be nil) and a
// function that returns a pointer to the same type. If the value is not nil,
// it is returned, otherwise the result of the function is returned.
//
// This is roughly equivalent to:
//
// value != nil ? value : fn()
//
// ...in languages that support the ternary operator.
func Value[T any](value *T, fn func() *T) *T {
if value != nil {
return value
}
return fn()
}

View File

@ -0,0 +1,64 @@
# API validation
This package holds functions which validate fields and types in the Kubernetes
API. It may be useful beyond API validation, but this is the primary goal.
Most of the public functions here have signatures which adhere to the following
pattern, which is assumed by automation and code-generation:
```
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func <Name>(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue <ValueType>, <OtherArgs...>) field.ErrorList
```
The name of validator functions should consider that callers will generally be
spelling out the package name and the function name, and so should aim for
legibility. E.g. `validate.Concept()`.
The `ctx` argument is Go's usual Context.
The `opCtx` argument provides information about the API operation in question.
The `fldPath` argument indicates the path to the field in question, to be used
in errors.
The `value` and `oldValue` arguments are the thing(s) being validated. For
CREATE operations (`opCtx.Operation == operation.Create`), the `oldValue`
argument will be nil. Many validators functions only look at the current value
(`value`) and disregard `oldValue`.
The `value` and `oldValue` arguments are always nilable - pointers to primitive
types, slices of any type, or maps of any type. Validator functions should
avoid dereferencing nil. Callers are expected to not pass a nil `value` unless the
API field itself was nilable. `oldValue` is always nil for CREATE operations and
is also nil for UPDATE operations if the `value` is not correlated with an `oldValue`.
Simple content-validators may have no `<OtherArgs>`, but validator functions
may take additional arguments. Some validator functions will be built as
generics, e.g. to allow any integer type or to handle arbitrary slices.
Examples:
```
// NonEmpty validates that a string is not empty.
func NonEmpty(ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList
// Even validates that a slice has an even number of items.
func Even[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList
// KeysMaxLen validates that all of the string keys in a map are under the
// specified length.
func KeysMaxLen[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ map[string]T, maxLen int) field.ErrorList
```
Validator functions always return an `ErrorList` where each item is a distinct
validation failure and a zero-length return value (not just nil) indicates
success.
Good validation failure messages follow the Kubernetes API conventions, for
example using "must" instead of "should".

View File

@ -0,0 +1,28 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ValidateFunc is a function that validates a value, possibly considering the
// old value (if any).
type ValidateFunc[T any] func(ctx context.Context, op operation.Operation, fldPath *field.Path, newValue, oldValue T) field.ErrorList

View File

@ -0,0 +1,32 @@
/*
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 constraints
// Signed is a constraint that permits any signed integer type.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint that permits any integer type.
type Integer interface {
Signed | Unsigned
}

View File

@ -0,0 +1,39 @@
/*
Copyright 2014 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 content
import (
"fmt"
"reflect"
"k8s.io/apimachinery/pkg/api/validate/constraints"
)
// MinError returns a string explanation of a "must be greater than or equal"
// validation failure.
func MinError[T constraints.Integer](min T) string {
return fmt.Sprintf("must be greater than or equal to %d", min)
}
// NEQError returns a string explanation of a "must not be equal to" validation failure.
func NEQError[T any](disallowed T) string {
format := "%v"
if reflect.ValueOf(disallowed).Kind() == reflect.String {
format = "%q"
}
return fmt.Sprintf("must not be equal to "+format, disallowed)
}

50
pkg/api/validate/doc.go Normal file
View File

@ -0,0 +1,50 @@
/*
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 validate holds API validation functions which are designed for use
// with the k8s.io/code-generator/cmd/validation-gen tool. Each validation
// function has a similar fingerprint:
//
// func <Name>(ctx context.Context,
// op operation.Operation,
// fldPath *field.Path,
// value, oldValue <nilable type>,
// <other args...>) field.ErrorList
//
// The value and oldValue arguments will always be a nilable type. If the
// original value was a string, these will be a *string. If the original value
// was a slice or map, these will be the same slice or map type.
//
// For a CREATE operation, the oldValue will always be nil. For an UPDATE
// operation, either value or oldValue may be nil, e.g. when adding or removing
// a value in a list-map. Validators which care about UPDATE operations should
// look at the opCtx argument to know which operation is being executed.
//
// Tightened validation (also known as ratcheting validation) is supported by
// defining a new validation function. For example:
//
// func TightenedMaxLength(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *string) field.ErrorList {
// if oldValue != nil && len(MaxLength(ctx, op, fldPath, oldValue, nil)) > 0 {
// // old value is not valid, so this value skips the tightened validation
// return nil
// }
// return MaxLength(ctx, op, fldPath, value, nil)
// }
//
// In general, we cannot distinguish a non-specified slice or map from one that
// is specified but empty. Validators should not rely on nil values, but use
// len() instead.
package validate

171
pkg/api/validate/each.go Normal file
View File

@ -0,0 +1,171 @@
/*
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 validate
import (
"context"
"sort"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// MatchFunc is a function that compares two values of the same type,
// according to some criteria, and returns true if they match.
type MatchFunc[T any] func(T, T) bool
// EachSliceVal performs validation on each element of newSlice using the provided validation function.
//
// For update operations, the match function finds corresponding values in oldSlice for each
// value in newSlice. This comparison can be either full or partial (e.g., matching only
// specific struct fields that serve as a unique identifier). If match is nil, validation
// proceeds without considering old values, and the equiv function is not used.
//
// For update operations, the equiv function checks if a new value is equivalent to its
// corresponding old value, enabling validation ratcheting. If equiv is nil but match is
// provided, the match function is assumed to perform full value comparison.
//
// Note: The slice element type must be non-nilable.
func EachSliceVal[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []T,
match, equiv MatchFunc[T], validator ValidateFunc[*T]) field.ErrorList {
var errs field.ErrorList
for i, val := range newSlice {
var old *T
if match != nil && len(oldSlice) > 0 {
old = lookup(oldSlice, val, match)
}
// If the operation is an update, for validation ratcheting, skip re-validating if the old
// value exists and either:
// 1. The match function provides full comparison (equiv is nil)
// 2. The equiv function confirms the values are equivalent (either directly or semantically)
//
// The equiv function provides equality comparison when match uses partial comparison.
if op.Type == operation.Update && old != nil && (equiv == nil || equiv(val, *old)) {
continue
}
errs = append(errs, validator(ctx, op, fldPath.Index(i), &val, old)...)
}
return errs
}
// lookup returns a pointer to the first element in the list that matches the
// target, according to the provided comparison function, or else nil.
func lookup[T any](list []T, target T, match MatchFunc[T]) *T {
for i := range list {
if match(list[i], target) {
return &list[i]
}
}
return nil
}
// EachMapVal validates each value in newMap using the specified validation
// function, passing the corresponding old value from oldMap if the key exists in oldMap.
// For update operations, it implements validation ratcheting by skipping validation
// when the old value exists and the equiv function confirms the values are equivalent.
// The value-type of the map is assumed to not be nilable.
// If equiv is nil, value-based ratcheting is disabled and all values will be validated.
func EachMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]V,
equiv MatchFunc[V], validator ValidateFunc[*V]) field.ErrorList {
var errs field.ErrorList
for key, val := range newMap {
var old *V
if o, found := oldMap[key]; found {
old = &o
}
// If the operation is an update, for validation ratcheting, skip re-validating if the old
// value is found and the equiv function confirms the values are equivalent.
if op.Type == operation.Update && old != nil && equiv != nil && equiv(val, *old) {
continue
}
errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), &val, old)...)
}
return errs
}
// EachMapKey validates each element of newMap with the specified
// validation function.
func EachMapKey[K ~string, T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]T,
validator ValidateFunc[*K]) field.ErrorList {
var errs field.ErrorList
for key := range newMap {
var old *K
if _, found := oldMap[key]; found {
old = &key
}
// If the operation is an update, for validation ratcheting, skip re-validating if
// the key is found in oldMap.
if op.Type == operation.Update && old != nil {
continue
}
// Note: the field path is the field, not the key.
errs = append(errs, validator(ctx, op, fldPath, &key, nil)...)
}
return errs
}
// Unique verifies that each element of newSlice is unique, according to the
// match function. It compares every element of the slice with every other
// element and returns errors for non-unique items.
func Unique[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, newSlice, _ []T, match MatchFunc[T]) field.ErrorList {
var dups []int
for i, val := range newSlice {
for j := i + 1; j < len(newSlice); j++ {
other := newSlice[j]
if match(val, other) {
if dups == nil {
dups = make([]int, 0, len(newSlice))
}
if lookup(dups, j, func(a, b int) bool { return a == b }) == nil {
dups = append(dups, j)
}
}
}
}
var errs field.ErrorList
sort.Ints(dups)
for _, i := range dups {
var val any = newSlice[i]
// TODO: we don't want the whole item to be logged in the error, just
// the key(s). Unfortunately, the way errors are rendered, it comes out
// as something like "map[string]any{...}" which is not very nice. Once
// that is fixed, we can consider adding a way for this function to
// specify that just the keys should be rendered in the error.
errs = append(errs, field.Duplicate(fldPath.Index(i), val))
}
return errs
}
// SemanticDeepEqual is a MatchFunc that uses equality.Semantic.DeepEqual to
// compare two values.
// This wrapper is needed because MatchFunc requires a function that takes two
// arguments of specific type T, while equality.Semantic.DeepEqual takes
// arguments of type interface{}/any. The wrapper satisfies the type
// constraints of MatchFunc while leveraging the underlying semantic equality
// logic. It can be used by any other function that needs to call DeepEqual.
func SemanticDeepEqual[T any](a, b T) bool {
return equality.Semantic.DeepEqual(a, b)
}
// DirectEqual is a MatchFunc that uses the == operator to compare two values.
// It can be used by any other function that needs to compare two values
// directly.
func DirectEqual[T comparable](a, b T) bool {
return a == b
}

View File

@ -0,0 +1,492 @@
/*
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 validate
import (
"context"
"fmt"
"reflect"
"slices"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
type TestStruct struct {
I int
D string
}
type TestStructWithKey struct {
Key string
I int
D string
}
type NonComparableKey struct {
I *int
}
type NonComparableStruct struct {
I int
S []string
}
type NonComparableStructWithKey struct {
Key string
I int
S []string
}
type NonComparableStructWithPtr struct {
I int
P *int
}
func TestEachSliceVal(t *testing.T) {
testEachSliceVal(t, "valid", []int{11, 12, 13})
testEachSliceVal(t, "valid", []string{"a", "b", "c"})
testEachSliceVal(t, "valid", []TestStruct{{11, "a"}, {12, "b"}, {13, "c"}})
testEachSliceVal(t, "empty", []int{})
testEachSliceVal(t, "empty", []string{})
testEachSliceVal(t, "empty", []TestStruct{})
testEachSliceVal[int](t, "nil", nil)
testEachSliceVal[string](t, "nil", nil)
testEachSliceVal[TestStruct](t, "nil", nil)
testEachSliceValUpdate(t, "valid", []int{11, 12, 13})
testEachSliceValUpdate(t, "valid", []string{"a", "b", "c"})
testEachSliceValUpdate(t, "valid", []TestStruct{{11, "a"}, {12, "b"}, {13, "c"}})
testEachSliceValUpdate(t, "empty", []int{})
testEachSliceValUpdate(t, "empty", []string{})
testEachSliceValUpdate(t, "empty", []TestStruct{})
testEachSliceValUpdate[int](t, "nil", nil)
testEachSliceValUpdate[string](t, "nil", nil)
testEachSliceValUpdate[TestStruct](t, "nil", nil)
}
func testEachSliceVal[T any](t *testing.T, name string, input []T) {
t.Helper()
var zero T
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
calls := 0
vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList {
if oldVal != nil {
t.Errorf("expected nil oldVal, got %v", *oldVal)
}
calls++
return nil
}
_ = EachSliceVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, nil, vfn)
if calls != len(input) {
t.Errorf("expected %d calls, got %d", len(input), calls)
}
})
}
func testEachSliceValUpdate[T any](t *testing.T, name string, input []T) {
t.Helper()
var zero T
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
calls := 0
vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList {
if oldVal == nil {
t.Fatalf("expected non-nil oldVal")
}
if !reflect.DeepEqual(*newVal, *oldVal) {
t.Errorf("expected oldVal == newVal, got %v, %v", *oldVal, *newVal)
}
calls++
return nil
}
old := make([]T, len(input))
copy(old, input)
slices.Reverse(old)
match := func(a, b T) bool { return reflect.DeepEqual(a, b) }
_ = EachSliceVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, old, match, match, vfn)
if calls != len(input) {
t.Errorf("expected %d calls, got %d", len(input), calls)
}
})
}
func TestEachSliceValRatcheting(t *testing.T) {
testEachSliceValRatcheting(t, "ComparableStruct same data different order",
[]TestStruct{
{11, "a"}, {12, "b"}, {13, "c"},
},
[]TestStruct{
{11, "a"}, {13, "c"}, {12, "b"},
},
SemanticDeepEqual,
nil,
)
testEachSliceValRatcheting(t, "ComparableStruct less data in new, exist in old",
[]TestStruct{
{11, "a"}, {12, "b"}, {13, "c"},
},
[]TestStruct{
{11, "a"}, {13, "c"},
},
DirectEqual,
nil,
)
testEachSliceValRatcheting(t, "Comparable struct with key same data different order",
[]TestStructWithKey{
{Key: "a", I: 11, D: "a"}, {Key: "b", I: 12, D: "b"}, {Key: "c", I: 13, D: "c"},
},
[]TestStructWithKey{
{Key: "a", I: 11, D: "a"}, {Key: "c", I: 13, D: "c"}, {Key: "b", I: 12, D: "b"},
},
MatchFunc[TestStructWithKey](func(a, b TestStructWithKey) bool {
return a.Key == b.Key
}),
DirectEqual,
)
testEachSliceValRatcheting(t, "Comparable struct with key less data in new, exist in old",
[]TestStructWithKey{
{Key: "a", I: 11, D: "a"}, {Key: "b", I: 12, D: "b"}, {Key: "c", I: 13, D: "c"},
},
[]TestStructWithKey{
{Key: "a", I: 11, D: "a"}, {Key: "c", I: 13, D: "c"},
},
MatchFunc[TestStructWithKey](func(a, b TestStructWithKey) bool {
return a.Key == b.Key
}),
DirectEqual,
)
testEachSliceValRatcheting(t, "NonComparableStruct same data different order",
[]NonComparableStruct{
{I: 11, S: []string{"a"}}, {I: 12, S: []string{"b"}}, {I: 13, S: []string{"c"}},
},
[]NonComparableStruct{
{I: 11, S: []string{"a"}}, {I: 13, S: []string{"c"}}, {I: 12, S: []string{"b"}},
},
SemanticDeepEqual,
nil,
)
testEachSliceValRatcheting(t, "NonComparableStructWithKey same data different order",
[]NonComparableStructWithKey{
{Key: "a", I: 11, S: []string{"a"}}, {Key: "b", I: 12, S: []string{"b"}}, {Key: "c", I: 13, S: []string{"c"}},
},
[]NonComparableStructWithKey{
{Key: "a", I: 11, S: []string{"a"}}, {Key: "b", I: 12, S: []string{"b"}}, {Key: "c", I: 13, S: []string{"c"}},
},
MatchFunc[NonComparableStructWithKey](func(a, b NonComparableStructWithKey) bool {
return a.Key == b.Key
}),
SemanticDeepEqual,
)
}
func testEachSliceValRatcheting[T any](t *testing.T, name string, old, new []T, match, equiv MatchFunc[T]) {
t.Helper()
var zero T
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList {
return field.ErrorList{field.Invalid(fldPath, *newVal, "expected no calls")}
}
errs := EachSliceVal(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, match, equiv, vfn)
if len(errs) > 0 {
t.Errorf("expected no errors, got %d: %s", len(errs), fmtErrs(errs))
}
})
}
func TestEachMapVal(t *testing.T) {
testEachMapVal(t, "valid", map[string]int{"one": 11, "two": 12, "three": 13})
testEachMapVal(t, "valid", map[string]string{"A": "a", "B": "b", "C": "c"})
testEachMapVal(t, "valid", map[string]TestStruct{"one": {11, "a"}, "two": {12, "b"}, "three": {13, "c"}})
testEachMapVal(t, "empty", map[string]int{})
testEachMapVal(t, "empty", map[string]string{})
testEachMapVal(t, "empty", map[string]TestStruct{})
testEachMapVal[int](t, "nil", nil)
testEachMapVal[string](t, "nil", nil)
testEachMapVal[TestStruct](t, "nil", nil)
}
func testEachMapVal[T any](t *testing.T, name string, input map[string]T) {
t.Helper()
var zero T
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
calls := 0
vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList {
if oldVal != nil {
t.Errorf("expected nil oldVal, got %v", *oldVal)
}
calls++
return nil
}
_ = EachMapVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, vfn)
if calls != len(input) {
t.Errorf("expected %d calls, got %d", len(input), calls)
}
})
}
func TestEachMapValRatcheting(t *testing.T) {
testEachMapValRatcheting(t, "primitive same data",
map[string]int{"one": 11, "two": 12, "three": 13},
map[string]int{"one": 11, "three": 13, "two": 12},
DirectEqual,
0,
)
testEachMapValRatcheting(t, "primitive less data in new, exist in old",
map[string]int{"one": 11, "two": 12, "three": 13},
map[string]int{"one": 11, "three": 13},
DirectEqual,
0,
)
testEachMapValRatcheting(t, "primitive new data, not exist in old",
map[string]int{"one": 11, "two": 12, "three": 13},
map[string]int{"one": 11, "three": 13, "two": 12, "four": 14},
DirectEqual,
1,
)
testEachMapValRatcheting(t, "non comparable value, same data",
map[string]NonComparableStruct{
"one": {I: 11, S: []string{"a"}},
"two": {I: 12, S: []string{"b"}},
"three": {I: 13, S: []string{"c"}},
},
map[string]NonComparableStruct{
"one": {I: 11, S: []string{"a"}},
"three": {I: 13, S: []string{"c"}},
"two": {I: 12, S: []string{"b"}},
},
SemanticDeepEqual,
0,
)
testEachMapValRatcheting(t, "non comparable value, less data in new, exist in old",
map[string]NonComparableStruct{
"one": {I: 11, S: []string{"a"}},
"two": {I: 12, S: []string{"b"}},
"three": {I: 13, S: []string{"c"}},
},
map[string]NonComparableStruct{
"one": {I: 11, S: []string{"a"}},
"three": {I: 13, S: []string{"c"}},
},
SemanticDeepEqual,
0,
)
testEachMapValRatcheting(t, "non comparable value, new data, not exist in old",
map[string]NonComparableStruct{
"one": {I: 11, S: []string{"a"}},
"two": {I: 12, S: []string{"b"}},
"three": {I: 13, S: []string{"c"}},
},
map[string]NonComparableStruct{
"one": {I: 11, S: []string{"a"}},
"three": {I: 13, S: []string{"c"}},
"two": {I: 12, S: []string{"b"}},
"four": {I: 14, S: []string{"d"}},
},
SemanticDeepEqual,
1,
)
testEachMapValRatcheting(t, "struct with pointer field, same value different pointer",
map[string]NonComparableStructWithPtr{
"one": {I: 11, P: ptr.To(1)},
"two": {I: 12, P: ptr.To(2)},
},
map[string]NonComparableStructWithPtr{
"one": {I: 11, P: ptr.To(1)},
"two": {I: 12, P: ptr.To(2)},
},
SemanticDeepEqual,
0,
)
testEachMapValRatcheting(t, "nil map to empty map",
nil,
map[string]int{},
DirectEqual,
0,
)
testEachMapValRatcheting(t, "nil map to non-empty map",
nil,
map[string]int{"one": 1},
DirectEqual,
1, // Expect validation for new entry
)
testEachMapValRatcheting(t, "empty map to nil map",
map[string]int{},
nil,
DirectEqual,
0,
)
testEachMapValRatcheting(t, "non-empty map to nil map",
map[string]int{"one": 1},
nil,
DirectEqual,
0,
)
}
func testEachMapValRatcheting[K ~string, V any](t *testing.T, name string, old, new map[K]V, equiv MatchFunc[V], wantCalls int) {
t.Helper()
var zero V
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
calls := 0
vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *V) field.ErrorList {
calls++
return nil
}
_ = EachMapVal(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, equiv, vfn)
if calls != wantCalls {
t.Errorf("expected %d calls, got %d", wantCalls, calls)
}
})
}
type StringType string
func TestEachMapKey(t *testing.T) {
testEachMapKey(t, "valid", map[string]int{"one": 11, "two": 12, "three": 13})
testEachMapKey(t, "valid", map[StringType]string{"A": "a", "B": "b", "C": "c"})
}
func testEachMapKey[K ~string, V any](t *testing.T, name string, input map[K]V) {
t.Helper()
var zero K
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
calls := 0
vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *K) field.ErrorList {
if oldVal != nil {
t.Errorf("expected nil oldVal, got %v", *oldVal)
}
calls++
return nil
}
_ = EachMapKey(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, vfn)
if calls != len(input) {
t.Errorf("expected %d calls, got %d", len(input), calls)
}
})
}
func TestEachMapKeyRatcheting(t *testing.T) {
testEachMapKeyRatcheting(t, "same data, 0 validation calls",
map[string]int{"one": 11, "two": 12, "three": 13},
map[string]int{"one": 11, "three": 13, "two": 12},
0,
)
testEachMapKeyRatcheting(t, "less data in new, exist in old, 0 validation calls",
map[string]int{"one": 11, "two": 12, "three": 13},
map[string]int{"one": 11, "three": 13},
0,
)
testEachMapKeyRatcheting(t, "new data, not exist in old, 1 validation call",
map[string]int{"one": 11, "two": 12, "three": 13},
map[string]int{"one": 11, "three": 13, "two": 12, "four": 14},
1,
)
}
func testEachMapKeyRatcheting[K ~string, V any](t *testing.T, name string, old, new map[K]V, wantCalls int) {
t.Helper()
var zero V
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
calls := 0
vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *K) field.ErrorList {
calls++
return nil
}
_ = EachMapKey(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, vfn)
if calls != wantCalls {
t.Errorf("expected %d calls, got %d", wantCalls, calls)
}
})
}
func TestUniqueComparableValues(t *testing.T) {
testUnique(t, "int_nil", []int(nil), 0)
testUnique(t, "int_empty", []int{}, 0)
testUnique(t, "int_uniq", []int{1, 2, 3}, 0)
testUnique(t, "int_dup", []int{1, 2, 3, 2, 1}, 2)
testUnique(t, "string_nil", []string(nil), 0)
testUnique(t, "string_empty", []string{}, 0)
testUnique(t, "string_uniq", []string{"a", "b", "c"}, 0)
testUnique(t, "string_dup", []string{"a", "a", "c", "b", "a"}, 2)
type isComparable struct {
I int
S string
}
testUnique(t, "struct_nil", []isComparable(nil), 0)
testUnique(t, "struct_empty", []isComparable{}, 0)
testUnique(t, "struct_uniq", []isComparable{{1, "a"}, {2, "b"}, {3, "c"}}, 0)
testUnique(t, "struct_dup", []isComparable{{1, "a"}, {2, "b"}, {3, "c"}, {2, "b"}, {1, "a"}}, 2)
}
func testUnique[T comparable](t *testing.T, name string, input []T, wantErrs int) {
t.Helper()
t.Run(fmt.Sprintf("%s(direct)", name), func(t *testing.T) {
errs := Unique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, DirectEqual)
if len(errs) != wantErrs {
t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs))
}
})
t.Run(fmt.Sprintf("%s(reflect)", name), func(t *testing.T) {
errs := Unique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, SemanticDeepEqual)
if len(errs) != wantErrs {
t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs))
}
})
}
func TestUniqueNonComparableValues(t *testing.T) {
type nonComparable struct {
I int
S []string
}
testUniqueByReflect(t, "noncomp_nil", []nonComparable(nil), 0)
testUniqueByReflect(t, "noncomp_empty", []nonComparable{}, 0)
testUniqueByReflect(t, "noncomp_uniq", []nonComparable{{1, []string{"a"}}, {2, []string{"b"}}, {3, []string{"c"}}}, 0)
testUniqueByReflect(t, "noncomp_dup", []nonComparable{
{1, []string{"a"}},
{2, []string{"b"}},
{3, []string{"c"}},
{2, []string{"b"}},
{1, []string{"a"}}}, 2)
}
func testUniqueByReflect[T any](t *testing.T, name string, input []T, wantErrs int) {
t.Helper()
var zero T
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
errs := Unique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, SemanticDeepEqual)
if len(errs) != wantErrs {
t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs))
}
})
}

40
pkg/api/validate/enum.go Normal file
View File

@ -0,0 +1,40 @@
/*
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 validate
import (
"context"
"slices"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// Enum verifies that the specified value is one of the valid symbols.
// This is for string enums only.
func Enum[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T, symbols sets.Set[T]) field.ErrorList {
if value == nil {
return nil
}
if !symbols.Has(*value) {
symbolList := symbols.UnsortedList()
slices.Sort(symbolList)
return field.ErrorList{field.NotSupported[T](fldPath, *value, symbolList)}
}
return nil
}

View File

@ -0,0 +1,107 @@
/*
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 validate
import (
"context"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestEnum(t *testing.T) {
cases := []struct {
value string
valid sets.Set[string]
err bool
}{{
value: "a",
valid: sets.New("a", "b", "c"),
err: false,
}, {
value: "x",
valid: sets.New("c", "a", "b"),
err: true,
}}
for i, tc := range cases {
result := Enum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &tc.value, nil, tc.valid)
if len(result) > 0 && !tc.err {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err {
t.Errorf("case %d: unexpected success", i)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if want, got := `supported values: "a", "b", "c"`, result[0].Detail; got != want {
t.Errorf("case %d: wrong error, expected: %q, got: %q", i, want, got)
}
}
}
}
func TestEnumTypedef(t *testing.T) {
type StringType string
const (
NotStringFoo StringType = "foo"
NotStringBar StringType = "bar"
NotStringQux StringType = "qux"
)
cases := []struct {
value StringType
valid sets.Set[StringType]
err bool
}{{
value: "foo",
valid: sets.New(NotStringFoo, NotStringBar, NotStringQux),
err: false,
}, {
value: "x",
valid: sets.New(NotStringFoo, NotStringBar, NotStringQux),
err: true,
}}
for i, tc := range cases {
result := Enum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &tc.value, nil, tc.valid)
if len(result) > 0 && !tc.err {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err {
t.Errorf("case %d: unexpected success", i)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if want, got := `supported values: "bar", "foo", "qux"`, result[0].Detail; got != want {
t.Errorf("case %d: wrong error, expected: %q, got: %q", i, want, got)
}
}
}
}

View File

@ -0,0 +1,38 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// NEQ validates that the specified comparable value is not equal to the disallowed value.
func NEQ[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, disallowed T) field.ErrorList {
if value == nil {
return nil
}
if *value == disallowed {
return field.ErrorList{
field.Invalid(fldPath, *value, content.NEQError(disallowed)).WithOrigin("neq"),
}
}
return nil
}

View File

@ -0,0 +1,64 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ImmutableByCompare verifies that the specified value has not changed in the
// course of an update operation. It does nothing if the old value is not
// provided. If the caller needs to compare types that are not trivially
// comparable, they should use ImmutableByReflect instead.
//
// Caution: structs with pointer fields satisfy comparable, but this function
// will only compare pointer values. It does not compare the pointed-to
// values.
func ImmutableByCompare[T comparable](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T) field.ErrorList {
if op.Type != operation.Update {
return nil
}
if value == nil && oldValue == nil {
return nil
}
if value == nil || oldValue == nil || *value != *oldValue {
return field.ErrorList{
field.Forbidden(fldPath, "field is immutable"),
}
}
return nil
}
// ImmutableByReflect verifies that the specified value has not changed in
// the course of an update operation. It does nothing if the old value is not
// provided. Unlike ImmutableByCompare, this function can be used with types that are
// not directly comparable, at the cost of performance.
func ImmutableByReflect[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue T) field.ErrorList {
if op.Type != operation.Update {
return nil
}
if !equality.Semantic.DeepEqual(value, oldValue) {
return field.ErrorList{
field.Forbidden(fldPath, "field is immutable"),
}
}
return nil
}

View File

@ -0,0 +1,250 @@
/*
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 validate
import (
"context"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
type StructComparable struct {
S string
I int
B bool
}
func TestImmutableByCompare(t *testing.T) {
structA := StructComparable{"abc", 123, true}
structA2 := structA
structB := StructComparable{"xyz", 456, false}
for _, tc := range []struct {
name string
fn func(operation.Operation, *field.Path) field.ErrorList
fail bool
}{{
name: "nil both values",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare[int](context.Background(), op, fld, nil, nil)
},
}, {
name: "nil value",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, nil, ptr.To(123))
},
fail: true,
}, {
name: "nil oldValue",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(123), nil)
},
fail: true,
}, {
name: "int",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(123), ptr.To(123))
},
}, {
name: "int fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(123), ptr.To(456))
},
fail: true,
}, {
name: "string",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To("abc"), ptr.To("abc"))
},
}, {
name: "string fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To("abc"), ptr.To("xyz"))
},
fail: true,
}, {
name: "bool",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(true), ptr.To(true))
},
}, {
name: "bool fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(true), ptr.To(false))
},
fail: true,
}, {
name: "same struct",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(structA), ptr.To(structA))
},
}, {
name: "equal struct",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(structA), ptr.To(structA2))
},
}, {
name: "struct fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByCompare(context.Background(), op, fld, ptr.To(structA), ptr.To(structB))
},
fail: true,
}} {
t.Run(tc.name, func(t *testing.T) {
errs := tc.fn(operation.Operation{Type: operation.Create}, field.NewPath(""))
if len(errs) != 0 { // Create should always succeed
t.Errorf("case %q (create): expected success: %v", tc.name, errs)
}
errs = tc.fn(operation.Operation{Type: operation.Update}, field.NewPath(""))
if tc.fail && len(errs) == 0 {
t.Errorf("case %q (update): expected failure", tc.name)
} else if !tc.fail && len(errs) != 0 {
t.Errorf("case %q (update): expected success: %v", tc.name, errs)
}
})
}
}
type StructNonComparable struct {
S string
SP *string
I int
IP *int
B bool
BP *bool
SS []string
MSS map[string]string
}
func TestImmutableByReflect(t *testing.T) {
structA := StructNonComparable{
S: "abc",
SP: ptr.To("abc"),
I: 123,
IP: ptr.To(123),
B: true,
BP: ptr.To(true),
SS: []string{"a", "b", "c"},
MSS: map[string]string{"a": "b", "c": "d"},
}
structA2 := structA
structA2.SP = ptr.To("abc")
structA2.IP = ptr.To(123)
structA2.BP = ptr.To(true)
structA2.SS = []string{"a", "b", "c"}
structA2.MSS = map[string]string{"a": "b", "c": "d"}
structB := StructNonComparable{
S: "xyz",
SP: ptr.To("xyz"),
I: 456,
IP: ptr.To(456),
B: false,
BP: ptr.To(false),
SS: []string{"x", "y", "z"},
MSS: map[string]string{"x": "X", "y": "Y"},
}
for _, tc := range []struct {
name string
fn func(operation.Operation, *field.Path) field.ErrorList
fail bool
}{{
name: "nil both values",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect[*int](context.Background(), op, fld, nil, nil)
},
}, {
name: "nil value",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, nil, ptr.To(123))
},
fail: true,
}, {
name: "nil oldValue",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(123), nil)
},
fail: true,
}, {
name: "int",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(123), ptr.To(123))
},
}, {
name: "int fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(123), ptr.To(456))
},
fail: true,
}, {
name: "string",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To("abc"), ptr.To("abc"))
},
}, {
name: "string fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To("abc"), ptr.To("xyz"))
},
fail: true,
}, {
name: "bool",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(true), ptr.To(true))
},
}, {
name: "bool fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(true), ptr.To(false))
},
fail: true,
}, {
name: "same struct",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(structA), ptr.To(structA))
},
}, {
name: "equal struct",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(structA), ptr.To(structA2))
},
}, {
name: "struct fail",
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
return ImmutableByReflect(context.Background(), op, fld, ptr.To(structA), ptr.To(structB))
},
fail: true,
}} {
t.Run(tc.name, func(t *testing.T) {
errs := tc.fn(operation.Operation{Type: operation.Create}, field.NewPath(""))
if len(errs) != 0 { // Create should always succeed
t.Errorf("case %q (create): expected success: %v", tc.name, errs)
}
errs = tc.fn(operation.Operation{Type: operation.Update}, field.NewPath(""))
if tc.fail && len(errs) == 0 {
t.Errorf("case %q (update): expected failure", tc.name)
} else if !tc.fail && len(errs) != 0 {
t.Errorf("case %q (update): expected success: %v", tc.name, errs)
}
})
}
}

72
pkg/api/validate/item.go Normal file
View File

@ -0,0 +1,72 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// MatchItemFn takes a pointer to an item and returns true if it matches the criteria.
type MatchItemFn[T any] func(*T) bool
// SliceItem finds the first item in newList that satisfies the 'matches' predicate,
// and if found, also looks for a matching item in oldList. It then invokes
// 'itemValidator' on these items.
// The fldPath passed to itemValidator is indexed to the matched item's position in newList.
// This function processes only the *first* matching item found in newList.
// It assumes that the 'matches' predicate targets a unique identifier (primary key) and
// will match at most one element per list.
// If this assumption is violated, changes in list order can lead this function
// to have inconsistent behavior.
// This function does not validate items that were removed (present in oldList but not in newList).
func SliceItem[TList ~[]TItem, TItem any](
ctx context.Context, op operation.Operation, fldPath *field.Path,
newList, oldList TList,
matches MatchItemFn[TItem],
equiv MatchFunc[TItem],
itemValidator func(ctx context.Context, op operation.Operation, fldPath *field.Path, newObj, oldObj *TItem) field.ErrorList,
) field.ErrorList {
var matchedNew, matchedOld *TItem
var newIndex int
for i := range newList {
if matches(&newList[i]) {
matchedNew = &newList[i]
newIndex = i
break
}
}
if matchedNew == nil {
return nil
}
for i := range oldList {
if matches(&oldList[i]) {
matchedOld = &oldList[i]
break
}
}
if op.Type == operation.Update && matchedOld != nil && equiv(*matchedNew, *matchedOld) {
return nil
}
return itemValidator(ctx, op, fldPath.Index(newIndex), matchedNew, matchedOld)
}

View File

@ -0,0 +1,167 @@
/*
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 validate
import (
"context"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
type multiKeyItem struct {
K1 string `json:"k1"`
K2 string `json:"k2"`
V int `json:"v"`
}
func TestSliceItem(t *testing.T) {
testCases := []struct {
name string
new []multiKeyItem
old []multiKeyItem
match MatchItemFn[multiKeyItem]
validator func(context.Context, operation.Operation, *field.Path, *multiKeyItem, *multiKeyItem) field.ErrorList
expected field.ErrorList
}{
{
name: "no match",
new: []multiKeyItem{
{K1: "a", K2: "1", V: 1},
},
match: func(i *multiKeyItem) bool {
return i.K1 == "target"
},
validator: func(_ context.Context, _ operation.Operation, fp *field.Path, _, _ *multiKeyItem) field.ErrorList {
return field.ErrorList{field.Invalid(fp, nil, "err")}
},
expected: nil,
},
{
name: "new item with matching keys",
new: []multiKeyItem{
{K1: "a", K2: "1", V: 1},
{K1: "target", K2: "target2", V: 2},
},
match: func(i *multiKeyItem) bool {
return i.K1 == "target" && i.K2 == "target2"
},
validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList {
if n != nil && o == nil {
return field.ErrorList{field.Invalid(fp, n.K1, "added")}
}
return nil
},
expected: field.ErrorList{field.Invalid(field.NewPath("").Index(1), "target", "added")},
},
{
name: "updated item - same keys different values",
new: []multiKeyItem{
{K1: "a", K2: "1", V: 1},
{K1: "update", K2: "target2", V: 20},
},
old: []multiKeyItem{
{K1: "a", K2: "1", V: 1},
{K1: "update", K2: "target2", V: 2},
},
match: func(i *multiKeyItem) bool {
return i.K1 == "update" && i.K2 == "target2"
},
validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList {
if n != nil && o != nil && n.V != o.V {
return field.ErrorList{field.Invalid(fp.Child("v"), n.V, "changed")}
}
return nil
},
expected: field.ErrorList{field.Invalid(field.NewPath("").Index(1).Child("v"), 20, "changed")},
},
{
// For completeness as listType=map && listKey=... required tags prevents dupes.
name: "first match only - multiple items with same keys",
new: []multiKeyItem{
{K1: "dup", K2: "target2", V: 1},
{K1: "dup", K2: "target2", V: 2},
},
old: []multiKeyItem{
{K1: "dup", K2: "target2", V: 10},
},
match: func(i *multiKeyItem) bool {
return i.K1 == "dup" && i.K2 == "target2"
},
validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList {
if n != nil && o != nil {
return field.ErrorList{field.Invalid(fp, n.V, "value")}
}
return nil
},
expected: field.ErrorList{field.Invalid(field.NewPath("").Index(0), 1, "value")},
},
{
name: "nil new list",
new: nil,
old: []multiKeyItem{
{K1: "exists", K2: "target2", V: 1},
},
match: func(i *multiKeyItem) bool {
return i.K1 == "exists" && i.K2 == "target2"
},
validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList {
if n == nil && o != nil {
return field.ErrorList{field.Invalid(fp, nil, "deleted")}
}
return nil
},
expected: nil,
},
{
name: "empty lists",
new: []multiKeyItem{},
old: []multiKeyItem{},
match: func(i *multiKeyItem) bool { return true },
validator: func(_ context.Context, _ operation.Operation, fp *field.Path, _, _ *multiKeyItem) field.ErrorList {
return field.ErrorList{field.Invalid(fp, nil, "err")}
},
expected: nil,
},
{
name: "nil lists",
new: nil,
old: nil,
match: func(i *multiKeyItem) bool { return true },
validator: func(_ context.Context, _ operation.Operation, fp *field.Path, _, _ *multiKeyItem) field.ErrorList {
return field.ErrorList{field.Invalid(fp, nil, "err")}
},
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
op := operation.Operation{Type: operation.Update}
fp := field.NewPath("")
got := SliceItem(ctx, op, fp, tc.new, tc.old, tc.match, SemanticDeepEqual, tc.validator)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
}
})
}
}

View File

@ -0,0 +1,37 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate/constraints"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// Minimum verifies that the specified value is greater than or equal to min.
func Minimum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min T) field.ErrorList {
if value == nil {
return nil
}
if *value < min {
return field.ErrorList{field.Invalid(fldPath, *value, content.MinError(min)).WithOrigin("minimum")}
}
return nil
}

View File

@ -0,0 +1,111 @@
/*
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 validate
import (
"context"
"regexp"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate/constraints"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestMinimum(t *testing.T) {
testMinimumPositive[int](t)
testMinimumNegative[int](t)
testMinimumPositive[int8](t)
testMinimumNegative[int8](t)
testMinimumPositive[int16](t)
testMinimumNegative[int16](t)
testMinimumPositive[int32](t)
testMinimumNegative[int32](t)
testMinimumPositive[int64](t)
testMinimumNegative[int64](t)
testMinimumPositive[uint](t)
testMinimumPositive[uint8](t)
testMinimumPositive[uint16](t)
testMinimumPositive[uint32](t)
testMinimumPositive[uint64](t)
}
type minimumTestCase[T constraints.Integer] struct {
value T
min T
err string // regex
}
func testMinimumPositive[T constraints.Integer](t *testing.T) {
t.Helper()
cases := []minimumTestCase[T]{{
value: 0,
min: 0,
}, {
value: 0,
min: 1,
err: "fldpath: Invalid value.*must be greater than or equal to",
}, {
value: 1,
min: 1,
}, {
value: 1,
min: 2,
err: "fldpath: Invalid value.*must be greater than or equal to",
}}
doTestMinimum[T](t, cases)
}
func testMinimumNegative[T constraints.Signed](t *testing.T) {
t.Helper()
cases := []minimumTestCase[T]{{
value: -1,
min: -1,
}, {
value: -2,
min: -1,
err: "fldpath: Invalid value.*must be greater than or equal to",
}}
doTestMinimum[T](t, cases)
}
func doTestMinimum[T constraints.Integer](t *testing.T, cases []minimumTestCase[T]) {
t.Helper()
for i, tc := range cases {
v := tc.value
result := Minimum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.min)
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}

View File

@ -0,0 +1,133 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// RequiredValue verifies that the specified value is not the zero-value for
// its type.
func RequiredValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
var zero T
if *value != zero {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// RequiredPointer verifies that the specified pointer is not nil.
func RequiredPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value != nil {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// RequiredSlice verifies that the specified slice is not empty.
func RequiredSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// RequiredMap verifies that the specified map is not empty.
func RequiredMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// ForbiddenValue verifies that the specified value is the zero-value for its
// type.
func ForbiddenValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
var zero T
if *value == zero {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// ForbiddenPointer verifies that the specified pointer is nil.
func ForbiddenPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// ForbiddenSlice verifies that the specified slice is empty.
func ForbiddenSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
if len(value) == 0 {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// ForbiddenMap verifies that the specified map is empty.
func ForbiddenMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
if len(value) == 0 {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// OptionalValue verifies that the specified value is not the zero-value for
// its type. This is identical to RequiredValue, but the caller should treat an
// error here as an indication that the optional value was not specified.
func OptionalValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
var zero T
if *value != zero {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}
// OptionalPointer verifies that the specified pointer is not nil. This is
// identical to RequiredPointer, but the caller should treat an error here as an
// indication that the optional value was not specified.
func OptionalPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value != nil {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}
// OptionalSlice verifies that the specified slice is not empty. This is
// identical to RequiredSlice, but the caller should treat an error here as an
// indication that the optional value was not specified.
func OptionalSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}
// OptionalMap verifies that the specified map is not empty. This is identical
// to RequiredMap, but the caller should treat an error here as an indication that
// the optional value was not specified.
func OptionalMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}

View File

@ -0,0 +1,924 @@
/*
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 validate
import (
"context"
"regexp"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
func TestRequiredValue(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := "value"
return RequiredValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := "" // zero-value
return RequiredValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 123
return RequiredValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 0 // zero-value
return RequiredValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := true
return RequiredValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := false // zero-value
return RequiredValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{"value"}
return RequiredValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{} // zero-value
return RequiredValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := ptr.To("")
return RequiredValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := (*string)(nil) // zero-value
return RequiredValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Required value",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestRequiredPointer(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := ""
return RequiredPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*string)(nil)
return RequiredPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 0
return RequiredPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*int)(nil)
return RequiredPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := false
return RequiredPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*bool)(nil)
return RequiredPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{}
return RequiredPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*struct{ S string })(nil)
return RequiredPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := (*string)(nil)
return RequiredPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (**string)(nil)
return RequiredPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath: Required value",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestRequiredSlice(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []string{""}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []string{}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []int{0}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []int{}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []bool{false}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []bool{}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []*string{nil}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []*string{}
return RequiredSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Required value",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestRequiredMap(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]string{"": ""}
return RequiredMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]string{}
return RequiredMap(context.Background(), op, fp, value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[int]int{0: 0}
return RequiredMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[int]int{}
return RequiredMap(context.Background(), op, fp, value, nil)
},
err: "fldpath: Required value",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[bool]bool{false: false}
return RequiredMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]bool{}
return RequiredMap(context.Background(), op, fp, value, nil)
},
err: "fldpath: Required value",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestOptionalValue(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := "value"
return OptionalValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := "" // zero-value
return OptionalValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 123
return OptionalValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 0 // zero-value
return OptionalValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := true
return OptionalValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := false // zero-value
return OptionalValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{"value"}
return OptionalValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{} // zero-value
return OptionalValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := ptr.To("")
return OptionalValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := (*string)(nil) // zero-value
return OptionalValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath:.*optional value was not specified",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestOptionalPointer(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := ""
return OptionalPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*string)(nil)
return OptionalPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 0
return OptionalPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*int)(nil)
return OptionalPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := false
return OptionalPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*bool)(nil)
return OptionalPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{}
return OptionalPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*struct{ S string })(nil)
return OptionalPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := (*string)(nil)
return OptionalPointer(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (**string)(nil)
return OptionalPointer(context.Background(), op, fp, pointer, nil)
},
err: "fldpath:.*optional value was not specified",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestOptionalSlice(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []string{""}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []string{}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []int{0}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []int{}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []bool{false}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []bool{}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []*string{nil}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []*string{}
return OptionalSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath:.*optional value was not specified",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestOptionalMap(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]string{"": ""}
return OptionalMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]string{}
return OptionalMap(context.Background(), op, fp, value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[int]int{0: 0}
return OptionalMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[int]int{}
return OptionalMap(context.Background(), op, fp, value, nil)
},
err: "fldpath:.*optional value was not specified",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[bool]bool{false: false}
return OptionalMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]bool{}
return OptionalMap(context.Background(), op, fp, value, nil)
},
err: "fldpath:.*optional value was not specified",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestForbiddenValue(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := ""
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := "value"
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 0
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 123
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := false
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := true
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{}
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{"value"}
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := (*string)(nil)
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := ptr.To("")
return ForbiddenValue(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestForbiddenPointer(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*string)(nil)
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := ""
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*int)(nil)
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := 0
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*bool)(nil)
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := false
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (*struct{ S string })(nil)
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := struct{ S string }{}
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
pointer := (**string)(nil)
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := (*string)(nil)
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
},
err: "fldpath: Forbidden",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestForbiddenSlice(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []string{}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []string{""}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []int{}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []int{0}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []bool{}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []bool{false}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []*string{}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := []*string{nil}
return ForbiddenSlice(context.Background(), op, fp, value, nil)
},
err: "fldpath: Forbidden",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}
func TestForbiddenMap(t *testing.T) {
cases := []struct {
fn func(op operation.Operation, fp *field.Path) field.ErrorList
err string // regex
}{{
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]string{}
return ForbiddenMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]string{"": ""}
return ForbiddenMap(context.Background(), op, fp, value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[int]int{}
return ForbiddenMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[int]int{0: 0}
return ForbiddenMap(context.Background(), op, fp, value, nil)
},
err: "fldpath: Forbidden",
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[string]bool{}
return ForbiddenMap(context.Background(), op, fp, value, nil)
},
}, {
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
value := map[bool]bool{false: false}
return ForbiddenMap(context.Background(), op, fp, value, nil)
},
err: "fldpath: Forbidden",
}}
for i, tc := range cases {
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
if len(result) > 0 && tc.err == "" {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && tc.err != "" {
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
}
}
}
}

View File

@ -0,0 +1,46 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// GetFieldFunc is a function that extracts a field from a type and returns a
// nilable value.
type GetFieldFunc[Tstruct any, Tfield any] func(*Tstruct) Tfield
// Subfield validates a subfield of a struct against a validator function.
func Subfield[Tstruct any, Tfield any](ctx context.Context, op operation.Operation, fldPath *field.Path, newStruct, oldStruct *Tstruct,
fldName string, getField GetFieldFunc[Tstruct, Tfield], validator ValidateFunc[Tfield]) field.ErrorList {
var errs field.ErrorList
newVal := getField(newStruct)
var oldVal Tfield
if oldStruct != nil {
oldVal = getField(oldStruct)
}
// TODO: passing an equiv function to Subfield for direct comparison instead of
// SemanticDeepEqual if fields can be compared directly, to improve performance.
if op.Type == operation.Update && SemanticDeepEqual(newVal, oldVal) {
return nil
}
errs = append(errs, validator(ctx, op, fldPath.Child(fldName), newVal, oldVal)...)
return errs
}

View File

@ -0,0 +1,35 @@
/*
Copyright 2014 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// FixedResult asserts a fixed boolean result. This is mostly useful for
// testing.
func FixedResult[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ T, result bool, arg string) field.ErrorList {
if result {
return nil
}
return field.ErrorList{
field.Invalid(fldPath, value, "forced failure: "+arg).WithOrigin("validateFalse"),
}
}

View File

@ -0,0 +1,146 @@
/*
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 validate
import (
"context"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
func TestFixedResult(t *testing.T) {
cases := []struct {
value any
pass bool
}{{
value: "",
pass: false,
}, {
value: "",
pass: true,
}, {
value: "nonempty",
pass: false,
}, {
value: "nonempty",
pass: true,
}, {
value: 0,
pass: false,
}, {
value: 0,
pass: true,
}, {
value: 1,
pass: false,
}, {
value: 1,
pass: true,
}, {
value: false,
pass: false,
}, {
value: false,
pass: true,
}, {
value: true,
pass: false,
}, {
value: true,
pass: true,
}, {
value: nil,
pass: false,
}, {
value: nil,
pass: true,
}, {
value: ptr.To(""),
pass: false,
}, {
value: ptr.To(""),
pass: true,
}, {
value: ptr.To("nonempty"),
pass: false,
}, {
value: ptr.To("nonempty"),
pass: true,
}, {
value: []string(nil),
pass: false,
}, {
value: []string(nil),
pass: true,
}, {
value: []string{},
pass: false,
}, {
value: []string{},
pass: true,
}, {
value: []string{"s"},
pass: false,
}, {
value: []string{"s"},
pass: true,
}, {
value: map[string]string(nil),
pass: false,
}, {
value: map[string]string(nil),
pass: true,
}, {
value: map[string]string{},
pass: false,
}, {
value: map[string]string{},
pass: true,
}, {
value: map[string]string{"k": "v"},
pass: false,
}, {
value: map[string]string{"k": "v"},
pass: true,
}}
matcher := field.ErrorMatcher{}.ByOrigin().ByDetailExact()
for i, tc := range cases {
result := FixedResult(context.Background(), operation.Operation{}, field.NewPath("fldpath"), tc.value, nil, tc.pass, "detail string")
if len(result) != 0 && tc.pass {
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
continue
}
if len(result) == 0 && !tc.pass {
t.Errorf("case %d: unexpected success", i)
continue
}
if len(result) > 0 {
if len(result) > 1 {
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
continue
}
wantErrorList := field.ErrorList{
field.Invalid(field.NewPath("fldpath"), tc.value, "forced failure: detail string").WithOrigin("validateFalse"),
}
matcher.Test(t, wantErrorList, result)
}
}
}

212
pkg/api/validate/union.go Normal file
View File

@ -0,0 +1,212 @@
/*
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 validate
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ExtractorFn extracts a value from a parent object. Depending on the context,
// that could be the value of a field or just whether that field was set or
// not.
// Note: obj is not guaranteed to be non-nil, need to handle nil obj in the
// extractor.
type ExtractorFn[T, V any] func(obj T) V
// UnionValidationOptions configures how union validation behaves
type UnionValidationOptions struct {
// ErrorForEmpty returns error when no fields are set (nil means no error)
ErrorForEmpty func(fldPath *field.Path, allFields []string) *field.Error
// ErrorForMultiple returns error when multiple fields are set (nil means no error)
ErrorForMultiple func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error
}
// Union verifies that exactly one member of a union is specified.
//
// UnionMembership must define all the members of the union.
//
// For example:
//
// var UnionMembershipForABC := validate.NewUnionMembership([2]string{"a", "A"}, [2]string{"b", "B"}, [2]string{"c", "C"})
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs fields.ErrorList) {
// errs = append(errs, Union(ctx, op, fldPath, in, oldIn, UnionMembershipForABC,
// func(in *ABC) bool { return in.A != nil },
// func(in *ABC) bool { return in.B != ""},
// func(in *ABC) bool { return in.C != 0 },
// )...)
// return errs
// }
func Union[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList {
options := UnionValidationOptions{
ErrorForEmpty: func(fldPath *field.Path, allFields []string) *field.Error {
return field.Invalid(fldPath, "",
fmt.Sprintf("must specify one of: %s", strings.Join(allFields, ", ")))
},
ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error {
return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")),
fmt.Sprintf("must specify exactly one of: %s", strings.Join(allFields, ", ")))
},
}
return unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...)
}
// DiscriminatedUnion verifies specified union member matches the discriminator.
//
// UnionMembership must define all the members of the union and the discriminator.
//
// For example:
//
// var UnionMembershipForABC = validate.NewDiscriminatedUnionMembership("type", [2]string{"a", "A"}, [2]string{"b", "B"}, [2]string{"c", "C"})
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
// errs = append(errs, DiscriminatedUnion(ctx, op, fldPath, in, oldIn, UnionMembershipForABC,
// func(in *ABC) string { return string(in.Type) },
// func(in *ABC) bool { return in.A != nil },
// func(in *ABC) bool { return in.B != ""},
// func(in *ABC) bool { return in.C != 0 },
// )...)
// return errs
// }
//
// It is not an error for the discriminatorValue to be unknown. That must be
// validated on its own.
func DiscriminatedUnion[T any, D ~string](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, discriminatorExtractor ExtractorFn[T, D], isSetFns ...ExtractorFn[T, bool]) (errs field.ErrorList) {
if len(union.members) != len(isSetFns) {
return field.ErrorList{
field.InternalError(fldPath,
fmt.Errorf("number of extractors (%d) does not match number of union members (%d)",
len(isSetFns), len(union.members))),
}
}
var changed bool
discriminatorValue := discriminatorExtractor(obj)
if op.Type == operation.Update {
oldDiscriminatorValue := discriminatorExtractor(oldObj)
changed = discriminatorValue != oldDiscriminatorValue
}
for i, fieldIsSet := range isSetFns {
member := union.members[i]
isDiscriminatedMember := string(discriminatorValue) == member.discriminatorValue
newIsSet := fieldIsSet(obj)
if op.Type == operation.Update && !changed {
oldIsSet := fieldIsSet(oldObj)
changed = changed || newIsSet != oldIsSet
}
if newIsSet && !isDiscriminatedMember {
errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "",
fmt.Sprintf("may only be specified when `%s` is %q", union.discriminatorName, member.discriminatorValue)))
} else if !newIsSet && isDiscriminatedMember {
errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "",
fmt.Sprintf("must be specified when `%s` is %q", union.discriminatorName, discriminatorValue)))
}
}
// If the union discriminator and membership is unchanged, we don't need to
// re-validate.
if op.Type == operation.Update && !changed {
return nil
}
return errs
}
type member struct {
fieldName, discriminatorValue string
}
// UnionMembership represents an ordered list of field union memberships.
type UnionMembership struct {
discriminatorName string
members []member
}
// NewUnionMembership returns a new UnionMembership for the given list of members.
//
// Each member is a [2]string to provide a fieldName and discriminatorValue pair, where
// [0] identifies the field name and [1] identifies the union member Name.
//
// Field names must be unique.
func NewUnionMembership(member ...[2]string) *UnionMembership {
return NewDiscriminatedUnionMembership("", member...)
}
// NewDiscriminatedUnionMembership returns a new UnionMembership for the given discriminator field and list of members.
// members are provided in the same way as for NewUnionMembership.
func NewDiscriminatedUnionMembership(discriminatorFieldName string, members ...[2]string) *UnionMembership {
u := &UnionMembership{}
u.discriminatorName = discriminatorFieldName
for _, fieldName := range members {
u.members = append(u.members, member{fieldName: fieldName[0], discriminatorValue: fieldName[1]})
}
return u
}
// allFields returns a string listing all the field names of the member of a union for use in error reporting.
func (u UnionMembership) allFields() []string {
memberNames := make([]string, 0, len(u.members))
for _, f := range u.members {
memberNames = append(memberNames, fmt.Sprintf("`%s`", f.fieldName))
}
return memberNames
}
func unionValidate[T any](op operation.Operation, fldPath *field.Path,
obj, oldObj T, union *UnionMembership, options UnionValidationOptions, isSetFns ...ExtractorFn[T, bool],
) field.ErrorList {
if len(union.members) != len(isSetFns) {
return field.ErrorList{
field.InternalError(fldPath,
fmt.Errorf("number of extractors (%d) does not match number of union members (%d)",
len(isSetFns), len(union.members))),
}
}
var specifiedFields []string
var changed bool
for i, fieldIsSet := range isSetFns {
newIsSet := fieldIsSet(obj)
if op.Type == operation.Update && !changed {
oldIsSet := fieldIsSet(oldObj)
changed = changed || newIsSet != oldIsSet
}
if newIsSet {
specifiedFields = append(specifiedFields, union.members[i].fieldName)
}
}
// If the union membership is unchanged, we don't need to re-validate.
if op.Type == operation.Update && !changed {
return nil
}
var errs field.ErrorList
if len(specifiedFields) > 1 && options.ErrorForMultiple != nil {
errs = append(errs, options.ErrorForMultiple(fldPath, specifiedFields, union.allFields()))
}
if len(specifiedFields) == 0 && options.ErrorForEmpty != nil {
errs = append(errs, options.ErrorForEmpty(fldPath, union.allFields()))
}
return errs
}

View File

@ -0,0 +1,328 @@
/*
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 validate
import (
"context"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
type testMember struct{}
func TestUnion(t *testing.T) {
testCases := []struct {
name string
fields [][2]string
fieldValues []bool
expected field.ErrorList
}{
{
name: "one member set",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{false, false, false, true},
expected: nil,
},
{
name: "two members set",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{false, true, false, true},
expected: field.ErrorList{field.Invalid(nil, "{b, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")},
},
{
name: "all members set",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{true, true, true, true},
expected: field.ErrorList{field.Invalid(nil, "{a, b, c, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")},
},
{
name: "no member set",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{false, false, false, false},
expected: field.ErrorList{field.Invalid(nil, "", "must specify one of: `a`, `b`, `c`, `d`")},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create mock extractors that return predefined values instead of
// actually extracting from the object.
extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues))
for i, val := range tc.fieldValues {
extractors[i] = func(_ *testMember) bool { return val }
}
got := Union(context.Background(), operation.Operation{}, nil, &testMember{}, nil, NewUnionMembership(tc.fields...), extractors...)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
}
})
}
}
func TestDiscriminatedUnion(t *testing.T) {
testCases := []struct {
name string
discriminatorField string
fields [][2]string
discriminatorValue string
fieldValues []bool
expected field.ErrorList
}{
{
name: "valid discriminated union A",
discriminatorField: "d",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
discriminatorValue: "A",
fieldValues: []bool{true, false, false, false},
},
{
name: "valid discriminated union C",
discriminatorField: "d",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
discriminatorValue: "C",
fieldValues: []bool{false, false, true, false},
},
{
name: "invalid, discriminator not set to member that is specified",
discriminatorField: "type",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
discriminatorValue: "C",
fieldValues: []bool{false, true, false, false},
expected: field.ErrorList{
field.Invalid(field.NewPath("b"), "", "may only be specified when `type` is \"B\""),
field.Invalid(field.NewPath("c"), "", "must be specified when `type` is \"C\""),
},
},
{
name: "invalid, discriminator correct, multiple members set",
discriminatorField: "type",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
discriminatorValue: "C",
fieldValues: []bool{false, true, true, true},
expected: field.ErrorList{
field.Invalid(field.NewPath("b"), "", "may only be specified when `type` is \"B\""),
field.Invalid(field.NewPath("d"), "", "may only be specified when `type` is \"D\""),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
discriminatorExtractor := func(_ *testMember) string { return tc.discriminatorValue }
// Create mock extractors that return predefined values instead of
// actually extracting from the object.
extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues))
for i, val := range tc.fieldValues {
extractors[i] = func(_ *testMember) bool { return val }
}
got := DiscriminatedUnion(context.Background(), operation.Operation{}, nil, &testMember{}, nil, NewDiscriminatedUnionMembership(tc.discriminatorField, tc.fields...), discriminatorExtractor, extractors...)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got.ToAggregate(), tc.expected.ToAggregate())
}
})
}
}
type testStruct struct {
M1 *m1 `json:"m1"`
M2 *m2 `json:"m2"`
}
type m1 struct{}
type m2 struct{}
var extractors = []ExtractorFn[*testStruct, bool]{
func(s *testStruct) bool {
if s == nil {
return false
}
return s.M1 != nil
},
func(s *testStruct) bool {
if s == nil {
return false
}
return s.M2 != nil
},
}
func TestUnionRatcheting(t *testing.T) {
testCases := []struct {
name string
oldStruct *testStruct
newStruct *testStruct
expected field.ErrorList
}{
{
name: "both nil",
oldStruct: nil,
newStruct: nil,
},
{
name: "both empty struct",
oldStruct: &testStruct{},
newStruct: &testStruct{},
},
{
name: "both have more than one member",
oldStruct: &testStruct{
M1: &m1{},
M2: &m2{},
},
newStruct: &testStruct{
M1: &m1{},
M2: &m2{},
},
},
{
name: "change to invalid",
oldStruct: &testStruct{
M1: &m1{},
},
newStruct: &testStruct{
M1: &m1{},
M2: &m2{},
},
expected: field.ErrorList{
field.Invalid(nil, "{m1, m2}", "must specify exactly one of: `m1`, `m2`"),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := Union(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, NewUnionMembership([][2]string{{"m1", "m1"}, {"m2", "m2"}}...), extractors...)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
}
})
}
}
type testDiscriminatedStruct struct {
D string `json:"d"`
M1 *m1 `json:"m1"`
M2 *m2 `json:"m2"`
}
var testDiscriminatorExtractor = func(s *testDiscriminatedStruct) string {
if s != nil {
return s.D
}
return ""
}
var testDiscriminatedExtractors = []ExtractorFn[*testDiscriminatedStruct, bool]{
func(s *testDiscriminatedStruct) bool {
if s == nil {
return false
}
return s.M1 != nil
},
func(s *testDiscriminatedStruct) bool {
if s == nil {
return false
}
return s.M2 != nil
},
}
func TestDiscriminatedUnionRatcheting(t *testing.T) {
testCases := []struct {
name string
oldStruct *testDiscriminatedStruct
newStruct *testDiscriminatedStruct
expected field.ErrorList
}{
{
name: "pass with both nil",
},
{
name: "pass with both empty struct",
oldStruct: &testDiscriminatedStruct{},
newStruct: &testDiscriminatedStruct{},
},
{
name: "pass with both not set to member that is specified",
oldStruct: &testDiscriminatedStruct{
D: "m1",
M2: &m2{},
},
newStruct: &testDiscriminatedStruct{
D: "m1",
M2: &m2{},
},
},
{
name: "pass with both set to more than one member",
oldStruct: &testDiscriminatedStruct{
D: "m1",
M1: &m1{},
M2: &m2{},
},
newStruct: &testDiscriminatedStruct{
D: "m1",
M1: &m1{},
M2: &m2{},
},
},
{
name: "fail on changing to invalid with both set",
oldStruct: &testDiscriminatedStruct{
D: "m1",
M1: &m1{},
},
newStruct: &testDiscriminatedStruct{
D: "m1",
M1: &m1{},
M2: &m2{},
},
expected: field.ErrorList{
field.Invalid(field.NewPath("m2"), "", "may only be specified when `d` is \"m2\""),
},
},
{
name: "fail on changing the discriminator",
oldStruct: &testDiscriminatedStruct{
D: "m1",
M1: &m1{},
},
newStruct: &testDiscriminatedStruct{
D: "m2",
M1: &m1{},
},
expected: field.ErrorList{
field.Invalid(field.NewPath("m1"), "", "may only be specified when `d` is \"m1\""),
field.Invalid(field.NewPath("m2"), "", "must be specified when `d` is \"m2\""),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := DiscriminatedUnion(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, NewDiscriminatedUnionMembership("d", [][2]string{{"m1", "m1"}, {"m2", "m2"}}...), testDiscriminatorExtractor, testDiscriminatedExtractors...)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
}
})
}
}

View File

@ -0,0 +1,41 @@
/*
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 validate
import (
"bytes"
"strconv"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// fmtErrs is a helper for nicer test output. It will use multiple lines if
// errs has more than 1 item.
func fmtErrs(errs field.ErrorList) string {
if len(errs) == 0 {
return "<no errors>"
}
if len(errs) == 1 {
return strconv.Quote(errs[0].Error())
}
buf := bytes.Buffer{}
for _, e := range errs {
buf.WriteString("\n")
buf.WriteString(strconv.Quote(e.Error()))
}
return buf.String()
}

View File

@ -0,0 +1,54 @@
/*
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 validate
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ZeroOrOneOfUnion verifies that at most one member of a union is specified.
//
// ZeroOrOneOfMembership must define all the members of the union.
//
// For example:
//
// var ZeroOrOneOfMembershipForABC = validate.NewUnionMembership([2]string{"a", "A"}, [2]string{"b", "B"}, [2]string{"c", "C"})
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
// errs = append(errs, ZeroOrOneOfUnion(ctx, op, fldPath, in, oldIn, UnionMembershipForABC,
// func(in *ABC) bool { return in.A != nil },
// func(in *ABC) bool { return in.B != ""},
// func(in *ABC) bool { return in.C != 0 },
// )...)
// return errs
// }
func ZeroOrOneOfUnion[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList {
options := UnionValidationOptions{
ErrorForEmpty: nil,
ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error {
return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")),
fmt.Sprintf("must specify at most one of: %s", strings.Join(allFields, ", "))).WithOrigin("zeroOrOneOf")
},
}
errs := unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...)
return errs
}

View File

@ -0,0 +1,145 @@
/*
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 validate
import (
"context"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestZeroOrOneOfUnion(t *testing.T) {
testCases := []struct {
name string
fields [][2]string
fieldValues []bool
expected field.ErrorList
}{
{
name: "one member set",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{false, false, false, true},
expected: nil,
},
{
name: "two members set",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{false, true, false, true},
expected: field.ErrorList{field.Invalid(nil, "{b, d}", "must specify at most one of: `a`, `b`, `c`, `d`").WithOrigin("zeroOrOneOf")},
},
{
name: "all members set",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{true, true, true, true},
expected: field.ErrorList{field.Invalid(nil, "{a, b, c, d}", "must specify at most one of: `a`, `b`, `c`, `d`").WithOrigin("zeroOrOneOf")},
},
{
name: "no member set - allowed for ZeroOrOneOf",
fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}},
fieldValues: []bool{false, false, false, false},
expected: nil, // This is the key difference from Union
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create mock extractors that return predefined values instead of
// actually extracting from the object.
extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues))
for i, val := range tc.fieldValues {
extractors[i] = func(_ *testMember) bool { return val }
}
got := ZeroOrOneOfUnion(context.Background(), operation.Operation{}, nil, &testMember{}, nil, NewUnionMembership(tc.fields...), extractors...)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
}
})
}
}
func TestZeroOrOneOfUnionRatcheting(t *testing.T) {
testCases := []struct {
name string
oldStruct *testStruct
newStruct *testStruct
expected field.ErrorList
}{
{
name: "both nil",
oldStruct: nil,
newStruct: nil,
},
{
name: "both empty struct - allowed for ZeroOrOneOf",
oldStruct: &testStruct{},
newStruct: &testStruct{},
},
{
name: "both have more than one member",
oldStruct: &testStruct{
M1: &m1{},
M2: &m2{},
},
newStruct: &testStruct{
M1: &m1{},
M2: &m2{},
},
},
{
name: "change to invalid",
oldStruct: &testStruct{
M1: &m1{},
},
newStruct: &testStruct{
M1: &m1{},
M2: &m2{},
},
expected: field.ErrorList{
field.Invalid(nil, "{m1, m2}", "must specify at most one of: `m1`, `m2`").WithOrigin("zeroOrOneOf"),
},
},
{
name: "change from empty to one member - allowed",
oldStruct: &testStruct{},
newStruct: &testStruct{
M1: &m1{},
},
expected: nil,
},
{
name: "change from one member to empty - allowed",
oldStruct: &testStruct{
M1: &m1{},
},
newStruct: &testStruct{},
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := ZeroOrOneOfUnion(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, NewUnionMembership([][2]string{{"m1", "m1"}, {"m2", "m2"}}...), extractors...)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
}
})
}
}

11
pkg/api/validation/OWNERS Normal file
View File

@ -0,0 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
# Disable inheritance as this is an api owners file
options:
no_parent_owners: true
approvers:
- api-approvers
reviewers:
- api-reviewers
labels:
- kind/api-change

View File

@ -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

View File

@ -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
}

View File

@ -50,7 +50,7 @@ func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) fie
}
}
if err := ValidateAnnotationsSize(annotations); err != nil {
allErrs = append(allErrs, field.TooLong(fldPath, "", TotalAnnotationSizeLimitB))
allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, TotalAnnotationSizeLimitB))
}
return allErrs
}
@ -74,13 +74,13 @@ func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field
allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty"))
}
if len(gvk.Kind) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty"))
allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "must not be empty"))
}
if len(ownerReference.Name) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty"))
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "must not be empty"))
}
if len(ownerReference.UID) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty"))
allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "must not be empty"))
}
if _, ok := BannedOwners[gvk]; ok {
allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk)))

View File

@ -328,19 +328,19 @@ func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: 1970-01-01 00:16:40 +0000 UTC: field is immutable"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:16:40Z\": field is immutable"},
},
"invalid clear deletionTimestamp": {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"null\": field is immutable"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: null: field is immutable"},
},
"invalid change deletionTimestamp": {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: 1970-01-01 00:33:20 +0000 UTC: field is immutable"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:33:20Z\": field is immutable"},
},
"invalid set deletionGracePeriodSeconds": {
@ -353,7 +353,7 @@ func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: \"null\": field is immutable"},
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: null: field is immutable"},
},
"invalid change deletionGracePeriodSeconds": {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
@ -373,7 +373,7 @@ func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
}
for i := range errs {
if errs[i].Error() != tc.ExpectedErrs[i] {
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
t.Errorf("%s: error #%d:\n expected: %q\n got: %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
}
}
if !reflect.DeepEqual(tc.New, tc.ExpectedNew) {
@ -419,7 +419,7 @@ func TestObjectMetaGenerationUpdate(t *testing.T) {
}
for i := range errList {
if errList[i] != tc.ExpectedErrs[i] {
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errList[i])
t.Errorf("%s: error #%d:\n expected: %q\n got: %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
}
}
}

43
pkg/apis/asn1/oid.go Normal file
View File

@ -0,0 +1,43 @@
/*
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 asn1
import "encoding/asn1"
// These constants store suffixes for use with the CNCF Private Enterprise Number allocated to Kubernetes:
// https://www.iana.org/assignments/enterprise-numbers.txt
//
// Root: 1.3.6.1.4.1.57683
//
// Cloud Native Computing Foundation
const (
// single-value, string value
x509UIDSuffix = 2
)
func makeOID(suffix int) asn1.ObjectIdentifier {
return asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57683, suffix}
}
// X509UID returns an OID (1.3.6.1.4.1.57683.2) for an element of an x509 distinguished name representing a user UID.
// The UID is a unique value for a particular user that will change if the user is removed from the system
// and another user is added with the same username.
//
// This element must not appear more than once in a distinguished name, and the value must be a string
func X509UID() asn1.ObjectIdentifier {
return makeOID(x509UIDSuffix)
}

View File

@ -23,7 +23,7 @@ import (
"strconv"
"strings"
fuzz "github.com/google/gofuzz"
"sigs.k8s.io/randfill"
apitesting "k8s.io/apimachinery/pkg/api/apitesting"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
@ -33,33 +33,34 @@ import (
"k8s.io/apimachinery/pkg/runtime"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
)
func genericFuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(q *resource.Quantity, c fuzz.Continue) {
func(q *resource.Quantity, c randfill.Continue) {
*q = *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent)
},
func(j *int, c fuzz.Continue) {
func(j *int, c randfill.Continue) {
*j = int(c.Int31())
},
func(j **int, c fuzz.Continue) {
if c.RandBool() {
func(j **int, c randfill.Continue) {
if c.Bool() {
i := int(c.Int31())
*j = &i
} else {
*j = nil
}
},
func(j *runtime.TypeMeta, c fuzz.Continue) {
func(j *runtime.TypeMeta, c randfill.Continue) {
// We have to customize the randomization of TypeMetas because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
},
func(j *runtime.Object, c fuzz.Continue) {
func(j *runtime.Object, c randfill.Continue) {
// TODO: uncomment when round trip starts from a versioned object
if true { //c.RandBool() {
if true { // c.Bool() {
*j = &runtime.Unknown{
// We do not set TypeMeta here because it is not carried through a round trip
Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`),
@ -68,15 +69,15 @@ func genericFuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
} else {
types := []runtime.Object{&metav1.Status{}, &metav1.APIGroup{}}
t := types[c.Rand.Intn(len(types))]
c.Fuzz(t)
c.Fill(t)
*j = t
}
},
func(r *runtime.RawExtension, c fuzz.Continue) {
func(r *runtime.RawExtension, c randfill.Continue) {
// Pick an arbitrary type and fuzz it
types := []runtime.Object{&metav1.Status{}, &metav1.APIGroup{}}
obj := types[c.Rand.Intn(len(types))]
c.Fuzz(obj)
c.Fill(obj)
// Find a codec for converting the object to raw bytes. This is necessary for the
// api version and kind to be correctly set be serialization.
@ -99,7 +100,7 @@ func genericFuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
}
}
// taken from gofuzz internals for RandString
// taken from randfill (nee gofuzz) internals for RandString
type charRange struct {
first, last rune
}
@ -113,7 +114,7 @@ func (c *charRange) choose(r *rand.Rand) rune {
// randomLabelPart produces a valid random label value or name-part
// of a label key.
func randomLabelPart(c fuzz.Continue, canBeEmpty bool) string {
func randomLabelPart(c randfill.Continue, canBeEmpty bool) string {
validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'}}
validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'},
{'.', '.'}, {'-', '-'}, {'_', '_'}}
@ -137,7 +138,7 @@ func randomLabelPart(c fuzz.Continue, canBeEmpty bool) string {
return string(runes)
}
func randomDNSLabel(c fuzz.Continue) string {
func randomDNSLabel(c randfill.Continue) string {
validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}}
validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'-', '-'}}
@ -153,11 +154,11 @@ func randomDNSLabel(c fuzz.Continue) string {
return string(runes)
}
func randomLabelKey(c fuzz.Continue) string {
func randomLabelKey(c randfill.Continue) string {
namePart := randomLabelPart(c, false)
prefixPart := ""
usePrefix := c.RandBool()
usePrefix := c.Bool()
if usePrefix {
// we can fit, with dots, at most 3 labels in the 253 allotted characters
prefixPartsLen := c.Rand.Intn(2) + 1
@ -174,28 +175,28 @@ func randomLabelKey(c fuzz.Continue) string {
func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(j *metav1.TypeMeta, c fuzz.Continue) {
func(j *metav1.TypeMeta, c randfill.Continue) {
// We have to customize the randomization of TypeMetas because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
},
func(j *metav1.ObjectMeta, c fuzz.Continue) {
c.FuzzNoCustom(j)
func(j *metav1.ObjectMeta, c randfill.Continue) {
c.FillNoCustom(j)
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
j.UID = types.UID(c.RandString())
j.ResourceVersion = strconv.FormatUint(c.Uint64(), 10)
j.UID = types.UID(c.String(0))
// Fuzzing sec and nsec in a smaller range (uint32 instead of int64),
// so that the result Unix time is a valid date and can be parsed into RFC3339 format.
var sec, nsec uint32
c.Fuzz(&sec)
c.Fuzz(&nsec)
c.Fill(&sec)
c.Fill(&nsec)
j.CreationTimestamp = metav1.Unix(int64(sec), int64(nsec)).Rfc3339Copy()
if j.DeletionTimestamp != nil {
c.Fuzz(&sec)
c.Fuzz(&nsec)
c.Fill(&sec)
c.Fill(&nsec)
t := metav1.Unix(int64(sec), int64(nsec)).Rfc3339Copy()
j.DeletionTimestamp = &t
}
@ -217,16 +218,16 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
j.Finalizers = nil
}
},
func(j *metav1.ResourceVersionMatch, c fuzz.Continue) {
func(j *metav1.ResourceVersionMatch, c randfill.Continue) {
matches := []metav1.ResourceVersionMatch{"", metav1.ResourceVersionMatchExact, metav1.ResourceVersionMatchNotOlderThan}
*j = matches[c.Rand.Intn(len(matches))]
},
func(j *metav1.ListMeta, c fuzz.Continue) {
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
j.SelfLink = c.RandString()
func(j *metav1.ListMeta, c randfill.Continue) {
j.ResourceVersion = strconv.FormatUint(c.Uint64(), 10)
j.SelfLink = c.String(0) //nolint:staticcheck // SA1019 backwards compatibility
},
func(j *metav1.LabelSelector, c fuzz.Continue) {
c.FuzzNoCustom(j)
func(j *metav1.LabelSelector, c randfill.Continue) {
c.FillNoCustom(j)
// we can't have an entirely empty selector, so force
// use of MatchExpression if necessary
if len(j.MatchLabels) == 0 && len(j.MatchExpressions) == 0 {
@ -249,13 +250,14 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
}
if j.MatchExpressions != nil {
// NB: the label selector parser code sorts match expressions by key, and sorts the values,
// so we need to make sure ours are sorted as well here to preserve round-trip comparison.
// NB: the label selector parser code sorts match expressions by key, and
// sorts and deduplicates the values, so we need to make sure ours are
// sorted and deduplicated as well here to preserve round-trip comparison.
// In practice, not sorting doesn't hurt anything...
for i := range j.MatchExpressions {
req := metav1.LabelSelectorRequirement{}
c.Fuzz(&req)
c.Fill(&req)
req.Key = randomLabelKey(c)
req.Operator = validOperators[c.Rand.Intn(len(validOperators))]
if req.Operator == metav1.LabelSelectorOpIn || req.Operator == metav1.LabelSelectorOpNotIn {
@ -266,7 +268,7 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
for i := range req.Values {
req.Values[i] = randomLabelPart(c, true)
}
sort.Strings(req.Values)
req.Values = sets.List(sets.New(req.Values...))
} else {
req.Values = nil
}
@ -276,8 +278,8 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
sort.Slice(j.MatchExpressions, func(a, b int) bool { return j.MatchExpressions[a].Key < j.MatchExpressions[b].Key })
}
},
func(j *metav1.ManagedFieldsEntry, c fuzz.Continue) {
c.FuzzNoCustom(j)
func(j *metav1.ManagedFieldsEntry, c randfill.Continue) {
c.FillNoCustom(j)
j.FieldsV1 = nil
},
}
@ -285,15 +287,15 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
func v1beta1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(r *metav1beta1.TableOptions, c fuzz.Continue) {
c.FuzzNoCustom(r)
func(r *metav1beta1.TableOptions, c randfill.Continue) {
c.FillNoCustom(r)
// NoHeaders is not serialized to the wire but is allowed within the versioned
// type because we don't use meta internal types in the client and API server.
r.NoHeaders = false
},
func(r *metav1beta1.TableRow, c fuzz.Continue) {
c.Fuzz(&r.Object)
c.Fuzz(&r.Conditions)
func(r *metav1beta1.TableRow, c randfill.Continue) {
c.Fill(&r.Object)
c.Fill(&r.Conditions)
if len(r.Conditions) == 0 {
r.Conditions = nil
}
@ -305,15 +307,15 @@ func v1beta1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
t := c.Intn(6)
switch t {
case 0:
r.Cells[i] = c.RandString()
r.Cells[i] = c.String(0)
case 1:
r.Cells[i] = c.Int63()
case 2:
r.Cells[i] = c.RandBool()
r.Cells[i] = c.Bool()
case 3:
x := map[string]interface{}{}
for j := c.Intn(10) + 1; j >= 0; j-- {
x[c.RandString()] = c.RandString()
x[c.String(0)] = c.String(0)
}
r.Cells[i] = x
case 4:

View File

@ -25,13 +25,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/utils/ptr"
)
func TestSetListOptionsDefaults(t *testing.T) {
boolPtrFn := func(b bool) *bool {
return &b
}
scenarios := []struct {
name string
watchListFeatureEnabled bool
@ -47,8 +44,8 @@ func TestSetListOptionsDefaults(t *testing.T) {
{
name: "no-op, SendInitialEvents set",
watchListFeatureEnabled: true,
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: boolPtrFn(true)},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: boolPtrFn(true)},
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)},
},
{
name: "no-op, ResourceVersionMatch set",
@ -66,13 +63,13 @@ func TestSetListOptionsDefaults(t *testing.T) {
name: "defaults applied, match on empty RV",
watchListFeatureEnabled: true,
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: boolPtrFn(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan},
},
{
name: "defaults applied, match on RV=0",
watchListFeatureEnabled: true,
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "0"},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "0", SendInitialEvents: boolPtrFn(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "0", SendInitialEvents: ptr.To(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan},
},
{
name: "no-op, match on empty RV but watch-list fg is off",
@ -82,14 +79,14 @@ func TestSetListOptionsDefaults(t *testing.T) {
{
name: "no-op, match on empty RV but SendInitialEvents is on",
watchListFeatureEnabled: true,
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: boolPtrFn(true)},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: boolPtrFn(true)},
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)},
},
{
name: "no-op, match on empty RV but SendInitialEvents is off",
watchListFeatureEnabled: true,
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: boolPtrFn(false)},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: boolPtrFn(false)},
targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(false)},
expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(false)},
},
{
name: "no-op, match on empty RV but ResourceVersionMatch set",

View File

@ -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

View File

@ -14,4 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package scheme // import "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
package scheme

View File

@ -24,16 +24,16 @@ import (
)
// Scheme is the registry for any type that adheres to the meta API spec.
var scheme = runtime.NewScheme()
var Scheme = runtime.NewScheme()
// Codecs provides access to encoding and decoding for the scheme.
var Codecs = serializer.NewCodecFactory(scheme)
var Codecs = serializer.NewCodecFactory(Scheme)
// ParameterCodec handles versioning of objects that are converted to query parameters.
var ParameterCodec = runtime.NewParameterCodec(scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
// Unlike other API groups, meta internal knows about all meta external versions, but keeps
// the logic for conversion private.
func init() {
utilruntime.Must(internalversion.AddToScheme(scheme))
utilruntime.Must(internalversion.AddToScheme(Scheme))
}

View File

@ -37,11 +37,11 @@ func TestListOptions(t *testing.T) {
Watch: true,
}
out := &metainternalversion.ListOptions{}
if err := scheme.Convert(in, out, nil); err != nil {
if err := Scheme.Convert(in, out, nil); err != nil {
t.Fatal(err)
}
actual := &metav1.ListOptions{}
if err := scheme.Convert(out, actual, nil); err != nil {
if err := Scheme.Convert(out, actual, nil); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(in, actual) {
@ -54,16 +54,16 @@ func TestListOptions(t *testing.T) {
{FieldSelector: "a!!!"},
} {
out = &metainternalversion.ListOptions{}
if err := scheme.Convert(failingObject, out, nil); err == nil {
if err := Scheme.Convert(failingObject, out, nil); err == nil {
t.Errorf("%d: unexpected conversion: %#v", i, out)
}
}
// verify kind registration
if gvks, unversioned, err := scheme.ObjectKinds(in); err != nil || unversioned || gvks[0] != metav1.SchemeGroupVersion.WithKind("ListOptions") {
if gvks, unversioned, err := Scheme.ObjectKinds(in); err != nil || unversioned || gvks[0] != metav1.SchemeGroupVersion.WithKind("ListOptions") {
t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err)
}
if gvks, unversioned, err := scheme.ObjectKinds(out); err != nil || unversioned || gvks[0] != metainternalversion.SchemeGroupVersion.WithKind("ListOptions") {
if gvks, unversioned, err := Scheme.ObjectKinds(out); err != nil || unversioned || gvks[0] != metainternalversion.SchemeGroupVersion.WithKind("ListOptions") {
t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err)
}

View File

@ -24,5 +24,5 @@ import (
)
func TestRoundTrip(t *testing.T) {
roundtrip.RoundTripTestForScheme(t, scheme, fuzzer.Funcs)
roundtrip.RoundTripTestForScheme(t, Scheme, fuzzer.Funcs)
}

View File

@ -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

View File

@ -21,13 +21,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)
func TestValidateListOptions(t *testing.T) {
boolPtrFn := func(b bool) *bool {
return &b
}
cases := []struct {
name string
opts internalversion.ListOptions
@ -65,7 +62,7 @@ func TestValidateListOptions(t *testing.T) {
}, {
name: "list-sendInitialEvents-forbidden",
opts: internalversion.ListOptions{
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
},
expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for list"},
}, {
@ -77,7 +74,7 @@ func TestValidateListOptions(t *testing.T) {
name: "valid-watch-sendInitialEvents-on",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
AllowWatchBookmarks: true,
},
@ -86,7 +83,7 @@ func TestValidateListOptions(t *testing.T) {
name: "valid-watch-sendInitialEvents-off",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(false),
SendInitialEvents: ptr.To(false),
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
AllowWatchBookmarks: true,
},
@ -102,14 +99,14 @@ func TestValidateListOptions(t *testing.T) {
name: "watch-sendInitialEvents-without-resourceversionmatch-forbidden",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
},
expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"},
}, {
name: "watch-sendInitialEvents-with-exact-resourceversionmatch-forbidden",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
ResourceVersionMatch: metav1.ResourceVersionMatchExact,
AllowWatchBookmarks: true,
},
@ -119,7 +116,7 @@ func TestValidateListOptions(t *testing.T) {
name: "watch-sendInitialEvents-on-with-empty-resourceversionmatch-forbidden",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
ResourceVersionMatch: "",
},
expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"},
@ -127,7 +124,7 @@ func TestValidateListOptions(t *testing.T) {
name: "watch-sendInitialEvents-off-with-empty-resourceversionmatch-forbidden",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(false),
SendInitialEvents: ptr.To(false),
ResourceVersionMatch: "",
},
expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"},
@ -135,7 +132,7 @@ func TestValidateListOptions(t *testing.T) {
name: "watch-sendInitialEvents-with-incorrect-resourceversionmatch-forbidden",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
ResourceVersionMatch: "incorrect",
AllowWatchBookmarks: true,
},
@ -147,7 +144,7 @@ func TestValidateListOptions(t *testing.T) {
name: "watch-sendInitialEvents-no-allowWatchBookmark",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
},
watchListFeatureEnabled: true,
@ -155,7 +152,7 @@ func TestValidateListOptions(t *testing.T) {
name: "watch-sendInitialEvents-no-watchlist-fg-disabled",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
AllowWatchBookmarks: true,
},
@ -164,7 +161,7 @@ func TestValidateListOptions(t *testing.T) {
name: "watch-sendInitialEvents-no-watchlist-fg-disabled",
opts: internalversion.ListOptions{
Watch: true,
SendInitialEvents: boolPtrFn(true),
SendInitialEvents: ptr.To(true),
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
AllowWatchBookmarks: true,
Continue: "123",

View File

@ -11,6 +11,7 @@ reviewers:
- luxas
- janetkuo
- justinsb
- ncdc
- soltysh
- dims
emeritus_reviewers:
- ncdc

View File

@ -18,6 +18,7 @@ package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"
)
// IsControlledBy checks if the object has a controllerRef set to the given owner
@ -36,10 +37,14 @@ func GetControllerOf(controllee Object) *OwnerReference {
return nil
}
cp := *ref
cp.Controller = ptr.To(*ref.Controller)
if ref.BlockOwnerDeletion != nil {
cp.BlockOwnerDeletion = ptr.To(*ref.BlockOwnerDeletion)
}
return &cp
}
// GetControllerOf returns a pointer to the controllerRef if controllee has a controller
// GetControllerOfNoCopy returns a pointer to the controllerRef if controllee has a controller
func GetControllerOfNoCopy(controllee Object) *OwnerReference {
refs := controllee.GetOwnerReferences()
for i := range refs {
@ -52,14 +57,12 @@ func GetControllerOfNoCopy(controllee Object) *OwnerReference {
// NewControllerRef creates an OwnerReference pointing to the given owner.
func NewControllerRef(owner Object, gvk schema.GroupVersionKind) *OwnerReference {
blockOwnerDeletion := true
isController := true
return &OwnerReference{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: owner.GetName(),
UID: owner.GetUID(),
BlockOwnerDeletion: &blockOwnerDeletion,
Controller: &isController,
BlockOwnerDeletion: ptr.To(true),
Controller: ptr.To(true),
}
}

View File

@ -69,6 +69,7 @@ func TestGetControllerOf(t *testing.T) {
},
}
controllerRef := NewControllerRef(obj1, gvk)
controllerRef.BlockOwnerDeletion = nil
var falseRef = false
obj2 := &metaObj{
ObjectMeta: ObjectMeta{
@ -95,6 +96,12 @@ func TestGetControllerOf(t *testing.T) {
if c.Name != controllerRef.Name || c.UID != controllerRef.UID {
t.Errorf("Incorrect result of GetControllerOf: %v", c)
}
// test that all pointers are also deep copied
if (c.Controller == controllerRef.Controller) ||
(c.BlockOwnerDeletion != nil && c.BlockOwnerDeletion == controllerRef.BlockOwnerDeletion) {
t.Errorf("GetControllerOf did not return deep copy: %v", c)
}
}
func BenchmarkGetControllerOf(b *testing.B) {

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ message APIGroup {
optional string name = 1;
// versions are the versions supported in this group.
// +listType=atomic
repeated GroupVersionForDiscovery versions = 2;
// preferredVersion is the version preferred by the API server, which
@ -49,6 +50,7 @@ message APIGroup {
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
// +optional
// +listType=atomic
repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 4;
}
@ -56,6 +58,7 @@ message APIGroup {
// /apis.
message APIGroupList {
// groups is a list of APIGroup.
// +listType=atomic
repeated APIGroup groups = 1;
}
@ -88,9 +91,11 @@ message APIResource {
optional Verbs verbs = 4;
// shortNames is a list of suggested short names of the resource.
// +listType=atomic
repeated string shortNames = 5;
// categories is a list of the grouped resources this resource belongs to (e.g. 'all')
// +listType=atomic
repeated string categories = 7;
// The hash value of the storage version, the version this resource is
@ -112,6 +117,7 @@ message APIResourceList {
optional string groupVersion = 1;
// resources contains the name of the resources and if they are namespaced.
// +listType=atomic
repeated APIResource resources = 2;
}
@ -122,6 +128,7 @@ message APIResourceList {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
message APIVersions {
// versions are the api versions that are available.
// +listType=atomic
repeated string versions = 1;
// a map of client CIDR to server address that is serving this group.
@ -131,6 +138,7 @@ message APIVersions {
// The server returns only those CIDRs that it thinks that the client can match.
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
// +listType=atomic
repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 2;
}
@ -145,6 +153,7 @@ message ApplyOptions {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
repeated string dryRun = 1;
// Force is going to "force" Apply requests. It means user will
@ -235,6 +244,7 @@ message CreateOptions {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
repeated string dryRun = 1;
// fieldManager is a name associated with the actor or entity
@ -303,7 +313,23 @@ message DeleteOptions {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
repeated string dryRun = 5;
// if set to true, it will trigger an unsafe deletion of the resource in
// case the normal deletion flow fails with a corrupt object error.
// A resource is considered corrupt if it can not be retrieved from
// the underlying storage successfully because of a) its data can
// not be transformed e.g. decryption failure, or b) it fails
// to decode into an object.
// NOTE: unsafe deletion ignores finalizer constraints, skips
// precondition checks, and removes the object from the storage.
// WARNING: This may potentially break the cluster if the workload
// associated with the resource being unsafe-deleted relies on normal
// deletion flow. Use only if you REALLY know what you are doing.
// The default value is false, and the user must opt in to enable it
// +optional
optional bool ignoreStoreReadErrorWithClusterBreakingPotential = 6;
}
// Duration is a wrapper around time.Duration which supports correct
@ -313,6 +339,25 @@ message Duration {
optional int64 duration = 1;
}
// FieldSelectorRequirement is a selector that contains values, a key, and an operator that
// relates the key and values.
message FieldSelectorRequirement {
// key is the field selector key that the requirement applies to.
optional string key = 1;
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists, DoesNotExist.
// The list of operators may grow in the future.
optional string operator = 2;
// values is an array of string values.
// If the operator is In or NotIn, the values array must be non-empty.
// If the operator is Exists or DoesNotExist, the values array must be empty.
// +optional
// +listType=atomic
repeated string values = 3;
}
// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.
//
// Each key is either a '.' representing the field itself, and will always map to an empty set,
@ -418,6 +463,7 @@ message LabelSelector {
// matchExpressions is a list of label selector requirements. The requirements are ANDed.
// +optional
// +listType=atomic
repeated LabelSelectorRequirement matchExpressions = 2;
}
@ -436,6 +482,7 @@ message LabelSelectorRequirement {
// the values array must be empty. This array is replaced during a strategic
// merge patch.
// +optional
// +listType=atomic
repeated string values = 3;
}
@ -447,7 +494,7 @@ message List {
optional ListMeta metadata = 1;
// List of objects
repeated k8s.io.apimachinery.pkg.runtime.RawExtension items = 2;
repeated .k8s.io.apimachinery.pkg.runtime.RawExtension items = 2;
}
// ListMeta describes metadata that synthetic resources must have, including lists and
@ -788,6 +835,8 @@ message ObjectMeta {
// +optional
// +patchMergeKey=uid
// +patchStrategy=merge
// +listType=map
// +listMapKey=uid
repeated OwnerReference ownerReferences = 13;
// Must be empty before the object is deleted from the registry. Each entry
@ -805,6 +854,7 @@ message ObjectMeta {
// are not vulnerable to ordering changes in the list.
// +optional
// +patchStrategy=merge
// +listType=set
repeated string finalizers = 14;
// ManagedFields maps workflow-id and version to the set of fields
@ -816,6 +866,7 @@ message ObjectMeta {
// workflow used when modifying the object.
//
// +optional
// +listType=atomic
repeated ManagedFieldsEntry managedFields = 17;
}
@ -890,6 +941,7 @@ message PatchOptions {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
repeated string dryRun = 1;
// Force is going to "force" Apply requests. It means user will
@ -943,6 +995,7 @@ message Preconditions {
// For example: "/healthz", "/apis".
message RootPaths {
// paths are the paths available at root.
// +listType=atomic
repeated string paths = 1;
}
@ -985,6 +1038,7 @@ message Status {
// is not guaranteed to conform to any schema except that defined by
// the reason type.
// +optional
// +listType=atomic
optional StatusDetails details = 5;
// Suggested HTTP return code for this status, 0 if not set.
@ -1049,6 +1103,7 @@ message StatusDetails {
// The Causes array includes more details associated with the StatusReason
// failure. Not all StatusReasons may provide detailed causes.
// +optional
// +listType=atomic
repeated StatusCause causes = 4;
// If specified, the time in seconds before the operation should be retried. Some errors may indicate
@ -1135,6 +1190,7 @@ message UpdateOptions {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
repeated string dryRun = 1;
// fieldManager is a name associated with the actor or entity
@ -1187,6 +1243,6 @@ message WatchEvent {
// * If Type is Deleted: the state of the object immediately before deletion.
// * If Type is Error: *Status is recommended; other types may make sense
// depending on context.
optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 2;
optional .k8s.io.apimachinery.pkg.runtime.RawExtension object = 2;
}

View File

@ -24,8 +24,10 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
utiljson "k8s.io/apimachinery/pkg/util/json"
)
// LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements
@ -280,13 +282,20 @@ func (f FieldsV1) MarshalJSON() ([]byte, error) {
if f.Raw == nil {
return []byte("null"), nil
}
if f.getContentType() == fieldsV1InvalidOrValidCBORObject {
var u map[string]interface{}
if err := cbor.Unmarshal(f.Raw, &u); err != nil {
return nil, fmt.Errorf("metav1.FieldsV1 cbor invalid: %w", err)
}
return utiljson.Marshal(u)
}
return f.Raw, nil
}
// UnmarshalJSON implements json.Unmarshaler
func (f *FieldsV1) UnmarshalJSON(b []byte) error {
if f == nil {
return errors.New("metav1.Fields: UnmarshalJSON on nil pointer")
return errors.New("metav1.FieldsV1: UnmarshalJSON on nil pointer")
}
if !bytes.Equal(b, []byte("null")) {
f.Raw = append(f.Raw[0:0], b...)
@ -296,3 +305,75 @@ func (f *FieldsV1) UnmarshalJSON(b []byte) error {
var _ json.Marshaler = FieldsV1{}
var _ json.Unmarshaler = &FieldsV1{}
func (f FieldsV1) MarshalCBOR() ([]byte, error) {
if f.Raw == nil {
return cbor.Marshal(nil)
}
if f.getContentType() == fieldsV1InvalidOrValidJSONObject {
var u map[string]interface{}
if err := utiljson.Unmarshal(f.Raw, &u); err != nil {
return nil, fmt.Errorf("metav1.FieldsV1 json invalid: %w", err)
}
return cbor.Marshal(u)
}
return f.Raw, nil
}
var cborNull = []byte{0xf6}
func (f *FieldsV1) UnmarshalCBOR(b []byte) error {
if f == nil {
return errors.New("metav1.FieldsV1: UnmarshalCBOR on nil pointer")
}
if !bytes.Equal(b, cborNull) {
f.Raw = append(f.Raw[0:0], b...)
}
return nil
}
const (
// fieldsV1InvalidOrEmpty indicates that a FieldsV1 either contains no raw bytes or its raw
// bytes don't represent an allowable value in any supported encoding.
fieldsV1InvalidOrEmpty = iota
// fieldsV1InvalidOrValidJSONObject indicates that a FieldV1 either contains raw bytes that
// are a valid JSON encoding of an allowable value or don't represent an allowable value in
// any supported encoding.
fieldsV1InvalidOrValidJSONObject
// fieldsV1InvalidOrValidCBORObject indicates that a FieldV1 either contains raw bytes that
// are a valid CBOR encoding of an allowable value or don't represent an allowable value in
// any supported encoding.
fieldsV1InvalidOrValidCBORObject
)
// getContentType returns one of fieldsV1InvalidOrEmpty, fieldsV1InvalidOrValidJSONObject,
// fieldsV1InvalidOrValidCBORObject based on the value of Raw.
//
// Raw can be encoded in JSON or CBOR and is only valid if it is empty, null, or an object (map)
// value. It is invalid if it contains a JSON string, number, boolean, or array. If Raw is nonempty
// and represents an allowable value, then the initial byte unambiguously distinguishes a
// JSON-encoded value from a CBOR-encoded value.
//
// A valid JSON-encoded value can begin with any of the four JSON whitespace characters, the first
// character 'n' of null, or '{' (0x09, 0x0a, 0x0d, 0x20, 0x6e, or 0x7b, respectively). A valid
// CBOR-encoded value can begin with the null simple value, an initial byte with major type "map",
// or, if a tag-enclosed map, an initial byte with major type "tag" (0xf6, 0xa0...0xbf, or
// 0xc6...0xdb). The two sets of valid initial bytes don't intersect.
func (f FieldsV1) getContentType() int {
if len(f.Raw) > 0 {
p := f.Raw[0]
switch p {
case 'n', '{', '\t', '\r', '\n', ' ':
return fieldsV1InvalidOrValidJSONObject
case 0xf6: // null
return fieldsV1InvalidOrValidCBORObject
default:
if p >= 0xa0 && p <= 0xbf /* map */ || p >= 0xc6 && p <= 0xdb /* tag */ {
return fieldsV1InvalidOrValidCBORObject
}
}
}
return fieldsV1InvalidOrEmpty
}

View File

@ -23,9 +23,9 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/randfill"
)
func TestLabelSelectorAsSelector(t *testing.T) {
@ -188,8 +188,8 @@ func TestResetObjectMetaForStatus(t *testing.T) {
existingMeta := &ObjectMeta{}
// fuzz the existingMeta to set every field, no nils
f := fuzz.New().NilChance(0).NumElements(1, 1).MaxDepth(10)
f.Fuzz(existingMeta)
f := randfill.New().NilChance(0).NumElements(1, 1).MaxDepth(10)
f.Fill(existingMeta)
ResetObjectMetaForStatus(meta, existingMeta)
// not all fields are stomped during the reset. These fields should not have been set. False
@ -249,3 +249,245 @@ func TestSetMetaDataLabel(t *testing.T) {
}
}
}
func TestFieldsV1MarshalJSON(t *testing.T) {
for _, tc := range []struct {
Name string
FieldsV1 FieldsV1
Want []byte
Error string
}{
{
Name: "nil encodes as json null",
FieldsV1: FieldsV1{},
Want: []byte(`null`),
},
{
Name: "empty invalid json is returned as-is",
FieldsV1: FieldsV1{Raw: []byte{}},
Want: []byte{},
},
{
Name: "cbor null is transcoded to json null",
FieldsV1: FieldsV1{Raw: []byte{0xf6}}, // null
Want: []byte(`null`),
},
{
Name: "valid non-map cbor and valid non-object json is returned as-is",
FieldsV1: FieldsV1{Raw: []byte{0x30}},
Want: []byte{0x30}, // Valid CBOR encoding of -17 and JSON encoding of 0!
},
{
Name: "self-described cbor map is transcoded to json map",
FieldsV1: FieldsV1{Raw: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}}, // 55799({"foo":"bar"})
Want: []byte(`{"foo":"bar"}`),
},
{
Name: "json object is returned as-is",
FieldsV1: FieldsV1{Raw: []byte(" \t\r\n{\"foo\":\"bar\"}")},
Want: []byte(" \t\r\n{\"foo\":\"bar\"}"),
},
{
Name: "invalid json is returned as-is",
FieldsV1: FieldsV1{Raw: []byte(`{{`)},
Want: []byte(`{{`),
},
{
Name: "invalid cbor fails to transcode to json",
FieldsV1: FieldsV1{Raw: []byte{0xa1}},
Error: "metav1.FieldsV1 cbor invalid: unexpected EOF",
},
} {
t.Run(tc.Name, func(t *testing.T) {
got, err := tc.FieldsV1.MarshalJSON()
if err != nil {
if tc.Error == "" {
t.Fatalf("unexpected error: %v", err)
}
if msg := err.Error(); msg != tc.Error {
t.Fatalf("expected error %q, got %q", tc.Error, msg)
}
} else if tc.Error != "" {
t.Fatalf("expected error %q, got nil", tc.Error)
}
if diff := cmp.Diff(tc.Want, got); diff != "" {
t.Errorf("unexpected diff:\n%s", diff)
}
})
}
}
func TestFieldsV1MarshalCBOR(t *testing.T) {
for _, tc := range []struct {
Name string
FieldsV1 FieldsV1
Want []byte
Error string
}{
{
Name: "nil encodes as cbor null",
FieldsV1: FieldsV1{},
Want: []byte{0xf6}, // null
},
{
Name: "empty invalid cbor is returned as-is",
FieldsV1: FieldsV1{Raw: []byte{}},
Want: []byte{},
},
{
Name: "json null is transcoded to cbor null",
FieldsV1: FieldsV1{Raw: []byte(`null`)},
Want: []byte{0xf6}, // null
},
{
Name: "valid non-map cbor and valid non-object json is returned as-is",
FieldsV1: FieldsV1{Raw: []byte{0x30}},
Want: []byte{0x30}, // Valid CBOR encoding of -17 and JSON encoding of 0!
},
{
Name: "json object is transcoded to cbor map",
FieldsV1: FieldsV1{Raw: []byte(" \t\r\n{\"foo\":\"bar\"}")},
Want: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'},
},
{
Name: "self-described cbor map is returned as-is",
FieldsV1: FieldsV1{Raw: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}}, // 55799({"foo":"bar"})
Want: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, // 55799({"foo":"bar"})
},
{
Name: "invalid json fails to transcode to cbor",
FieldsV1: FieldsV1{Raw: []byte(`{{`)},
Error: "metav1.FieldsV1 json invalid: invalid character '{' looking for beginning of object key string",
},
{
Name: "invalid cbor is returned as-is",
FieldsV1: FieldsV1{Raw: []byte{0xa1}},
Want: []byte{0xa1},
},
} {
t.Run(tc.Name, func(t *testing.T) {
got, err := tc.FieldsV1.MarshalCBOR()
if err != nil {
if tc.Error == "" {
t.Fatalf("unexpected error: %v", err)
}
if msg := err.Error(); msg != tc.Error {
t.Fatalf("expected error %q, got %q", tc.Error, msg)
}
} else if tc.Error != "" {
t.Fatalf("expected error %q, got nil", tc.Error)
}
if diff := cmp.Diff(tc.Want, got); diff != "" {
t.Errorf("unexpected diff:\n%s", diff)
}
})
}
}
func TestFieldsV1UnmarshalJSON(t *testing.T) {
for _, tc := range []struct {
Name string
JSON []byte
Into *FieldsV1
Want *FieldsV1
Error string
}{
{
Name: "nil receiver returns error",
Into: nil,
Error: "metav1.FieldsV1: UnmarshalJSON on nil pointer",
},
{
Name: "json null does not modify receiver", // conventional for json.Unmarshaler
JSON: []byte(`null`),
Into: &FieldsV1{Raw: []byte(`unmodified`)},
Want: &FieldsV1{Raw: []byte(`unmodified`)},
},
{
Name: "valid input is copied verbatim",
JSON: []byte("{\"foo\":\"bar\"} \t\r\n"),
Into: &FieldsV1{},
Want: &FieldsV1{Raw: []byte("{\"foo\":\"bar\"} \t\r\n")},
},
{
Name: "invalid input is copied verbatim",
JSON: []byte("{{"),
Into: &FieldsV1{},
Want: &FieldsV1{Raw: []byte("{{")},
},
} {
t.Run(tc.Name, func(t *testing.T) {
got := tc.Into.DeepCopy()
err := got.UnmarshalJSON(tc.JSON)
if err != nil {
if tc.Error == "" {
t.Fatalf("unexpected error: %v", err)
}
if msg := err.Error(); msg != tc.Error {
t.Fatalf("expected error %q, got %q", tc.Error, msg)
}
} else if tc.Error != "" {
t.Fatalf("expected error %q, got nil", tc.Error)
}
if diff := cmp.Diff(tc.Want, got); diff != "" {
t.Errorf("unexpected diff:\n%s", diff)
}
})
}
}
func TestFieldsV1UnmarshalCBOR(t *testing.T) {
for _, tc := range []struct {
Name string
CBOR []byte
Into *FieldsV1
Want *FieldsV1
Error string
}{
{
Name: "nil receiver returns error",
Into: nil,
Want: nil,
Error: "metav1.FieldsV1: UnmarshalCBOR on nil pointer",
},
{
Name: "cbor null does not modify receiver",
CBOR: []byte{0xf6},
Into: &FieldsV1{Raw: []byte(`unmodified`)},
Want: &FieldsV1{Raw: []byte(`unmodified`)},
},
{
Name: "valid input is copied verbatim",
CBOR: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'},
Into: &FieldsV1{},
Want: &FieldsV1{Raw: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}},
},
{
Name: "invalid input is copied verbatim",
CBOR: []byte{0xff}, // UnmarshalCBOR should never be called with malformed input, testing anyway.
Into: &FieldsV1{},
Want: &FieldsV1{Raw: []byte{0xff}},
},
} {
t.Run(tc.Name, func(t *testing.T) {
got := tc.Into.DeepCopy()
err := got.UnmarshalCBOR(tc.CBOR)
if err != nil {
if tc.Error == "" {
t.Fatalf("unexpected error: %v", err)
}
if msg := err.Error(); msg != tc.Error {
t.Fatalf("expected error %q, got %q", tc.Error, msg)
}
} else if tc.Error != "" {
t.Fatalf("expected error %q, got nil", tc.Error)
}
if diff := cmp.Diff(tc.Want, got); diff != "" {
t.Errorf("unexpected diff:\n%s", diff)
}
})
}
}

View File

@ -19,6 +19,8 @@ package v1
import (
"encoding/json"
"time"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
)
const RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00"
@ -129,6 +131,25 @@ func (t *MicroTime) UnmarshalJSON(b []byte) error {
return nil
}
func (t *MicroTime) UnmarshalCBOR(b []byte) error {
var s *string
if err := cbor.Unmarshal(b, &s); err != nil {
return err
}
if s == nil {
t.Time = time.Time{}
return nil
}
parsed, err := time.Parse(RFC3339Micro, *s)
if err != nil {
return err
}
t.Time = parsed.Local()
return nil
}
// UnmarshalQueryParameter converts from a URL query parameter value to an object
func (t *MicroTime) UnmarshalQueryParameter(str string) error {
if len(str) == 0 {
@ -160,6 +181,13 @@ func (t MicroTime) MarshalJSON() ([]byte, error) {
return json.Marshal(t.UTC().Format(RFC3339Micro))
}
func (t MicroTime) MarshalCBOR() ([]byte, error) {
if t.IsZero() {
return cbor.Marshal(nil)
}
return cbor.Marshal(t.UTC().Format(RFC3339Micro))
}
// OpenAPISchemaType is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
//

View File

@ -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{}

View File

@ -18,11 +18,16 @@ package v1
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
"sigs.k8s.io/yaml"
"github.com/google/go-cmp/cmp"
"sigs.k8s.io/randfill"
)
type MicroTimeHolder struct {
@ -113,6 +118,59 @@ func TestMicroTimeUnmarshalJSON(t *testing.T) {
}
}
func TestMicroTimeMarshalCBOR(t *testing.T) {
for _, tc := range []struct {
name string
in MicroTime
out []byte
}{
{name: "zero value", in: MicroTime{}, out: []byte{0xf6}}, // null
{name: "no fractional seconds", in: DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.UTC), out: []byte("\x58\x1b1998-05-05T05:05:05.000000Z")}, // '1998-05-05T05:05:05.000000Z'
{name: "nanoseconds truncated", in: DateMicro(1998, time.May, 5, 5, 5, 5, 5050, time.UTC), out: []byte("\x58\x1b1998-05-05T05:05:05.000005Z")}, // '1998-05-05T05:05:05.000005Z'
} {
t.Run(fmt.Sprintf("%+v", tc.in), func(t *testing.T) {
got, err := tc.in.MarshalCBOR()
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(tc.out, got); diff != "" {
t.Errorf("unexpected output:\n%s", diff)
}
})
}
}
func TestMicroTimeUnmarshalCBOR(t *testing.T) {
for _, tc := range []struct {
name string
in []byte
out MicroTime
errMessage string
}{
{name: "null", in: []byte{0xf6}, out: MicroTime{}}, // null
{name: "valid", in: []byte("\x58\x1b1998-05-05T05:05:05.000000Z"), out: MicroTime{Time: Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, // '1998-05-05T05:05:05.000000Z'
{name: "invalid cbor type", in: []byte{0x07}, out: MicroTime{}, errMessage: "cbor: cannot unmarshal positive integer into Go value of type string"}, // 7
{name: "malformed timestamp", in: []byte("\x45hello"), out: MicroTime{}, errMessage: `parsing time "hello" as "2006-01-02T15:04:05.000000Z07:00": cannot parse "hello" as "2006"`}, // 'hello'
} {
t.Run(tc.name, func(t *testing.T) {
var got MicroTime
err := got.UnmarshalCBOR(tc.in)
if err != nil {
if tc.errMessage == "" {
t.Fatalf("want nil error, got: %v", err)
} else if gotMessage := err.Error(); tc.errMessage != gotMessage {
t.Fatalf("want error: %q, got: %q", tc.errMessage, gotMessage)
}
} else if tc.errMessage != "" {
t.Fatalf("got nil error, want: %s", tc.errMessage)
}
if diff := cmp.Diff(tc.out, got); diff != "" {
t.Errorf("unexpected output:\n%s", diff)
}
})
}
}
func TestMicroTimeProto(t *testing.T) {
cases := []struct {
input MicroTime
@ -318,3 +376,27 @@ func TestMicroTimeProtoUnmarshalRaw(t *testing.T) {
}
}
func TestMicroTimeRoundtripCBOR(t *testing.T) {
fuzzer := randfill.New()
for i := 0; i < 500; i++ {
var initial, final MicroTime
fuzzer.Fill(&initial)
b, err := cbor.Marshal(initial)
if err != nil {
t.Errorf("error encoding %v: %v", initial, err)
continue
}
err = cbor.Unmarshal(b, &final)
if err != nil {
t.Errorf("%v: error decoding %v: %v", initial, string(b), err)
}
if !final.Equal(&initial) {
diag, err := cbor.Diagnose(b)
if err != nil {
t.Logf("failed to produce diagnostic encoding of 0x%x: %v", b, err)
}
t.Errorf("expected equal: %v, %v (cbor was '%s')", initial, final, diag)
}
}
}

View File

@ -22,15 +22,15 @@ import (
"reflect"
"testing"
fuzz "github.com/google/gofuzz"
"sigs.k8s.io/randfill"
)
func TestPatchOptionsIsSuperSetOfUpdateOptions(t *testing.T) {
f := fuzz.New()
f := randfill.New()
for i := 0; i < 1000; i++ {
t.Run(fmt.Sprintf("Run %d/1000", i), func(t *testing.T) {
update := UpdateOptions{}
f.Fuzz(&update)
f.Fill(&update)
b, err := json.Marshal(update)
if err != nil {

View File

@ -19,6 +19,8 @@ package v1
import (
"encoding/json"
"time"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
)
// Time is a wrapper around time.Time which supports correct
@ -116,6 +118,25 @@ func (t *Time) UnmarshalJSON(b []byte) error {
return nil
}
func (t *Time) UnmarshalCBOR(b []byte) error {
var s *string
if err := cbor.Unmarshal(b, &s); err != nil {
return err
}
if s == nil {
t.Time = time.Time{}
return nil
}
parsed, err := time.Parse(time.RFC3339, *s)
if err != nil {
return err
}
t.Time = parsed.Local()
return nil
}
// UnmarshalQueryParameter converts from a URL query parameter value to an object
func (t *Time) UnmarshalQueryParameter(str string) error {
if len(str) == 0 {
@ -151,6 +172,14 @@ func (t Time) MarshalJSON() ([]byte, error) {
return buf, nil
}
func (t Time) MarshalCBOR() ([]byte, error) {
if t.IsZero() {
return cbor.Marshal(nil)
}
return cbor.Marshal(t.UTC().Format(time.RFC3339))
}
// ToUnstructured implements the value.UnstructuredConverter interface.
func (t Time) ToUnstructured() interface{} {
if t.IsZero() {

View File

@ -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{}

View File

@ -18,11 +18,16 @@ package v1
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
"sigs.k8s.io/yaml"
"github.com/google/go-cmp/cmp"
"sigs.k8s.io/randfill"
)
type TimeHolder struct {
@ -100,6 +105,7 @@ func TestTimeUnmarshalJSON(t *testing.T) {
}{
{"{\"t\":null}", Time{}},
{"{\"t\":\"1998-05-05T05:05:05Z\"}", Time{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}},
{"{\"t\":\"1998-05-05T05:05:05.123456789Z\"}", Time{Date(1998, time.May, 5, 5, 5, 5, 123456789, time.UTC).Local()}},
}
for _, c := range cases {
@ -147,6 +153,62 @@ func TestTimeMarshalJSONUnmarshalYAML(t *testing.T) {
}
}
func TestTimeMarshalCBOR(t *testing.T) {
for _, tc := range []struct {
name string
in Time
out []byte
}{
{name: "zero value", in: Time{}, out: []byte{0xf6}}, // null
{name: "no fractional seconds", in: Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC), out: []byte("\x541998-05-05T05:05:05Z")}, // '1998-05-05T05:05:05Z'
{name: "fractional seconds truncated", in: Date(1998, time.May, 5, 5, 5, 5, 123456789, time.UTC), out: []byte("\x541998-05-05T05:05:05Z")}, // '1998-05-05T05:05:05Z'
{name: "epoch", in: Time{Time: time.Unix(0, 0)}, out: []byte("\x541970-01-01T00:00:00Z")}, // '1970-01-01T00:00:00Z'
{name: "pre-epoch", in: Date(1960, time.January, 1, 0, 0, 0, 0, time.UTC), out: []byte("\x541960-01-01T00:00:00Z")}, // '1960-01-01T00:00:00Z'
} {
t.Run(fmt.Sprintf("%+v", tc.in), func(t *testing.T) {
got, err := tc.in.MarshalCBOR()
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(tc.out, got); diff != "" {
t.Errorf("unexpected output:\n%s", diff)
}
})
}
}
func TestTimeUnmarshalCBOR(t *testing.T) {
for _, tc := range []struct {
name string
in []byte
out Time
errMessage string
}{
{name: "null", in: []byte{0xf6}, out: Time{}}, // null
{name: "no fractional seconds", in: []byte("\x58\x141998-05-05T05:05:05Z"), out: Time{Time: Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, // '1998-05-05T05:05:05Z'
{name: "fractional seconds", in: []byte("\x58\x1e1998-05-05T05:05:05.123456789Z"), out: Time{Time: Date(1998, time.May, 5, 5, 5, 5, 123456789, time.UTC).Local()}}, // '1998-05-05T05:05:05.123456789Z'
{name: "invalid cbor type", in: []byte{0x07}, out: Time{}, errMessage: "cbor: cannot unmarshal positive integer into Go value of type string"}, // 7
{name: "malformed timestamp", in: []byte("\x45hello"), out: Time{}, errMessage: `parsing time "hello" as "2006-01-02T15:04:05Z07:00": cannot parse "hello" as "2006"`}, // 'hello'
} {
t.Run(tc.name, func(t *testing.T) {
var got Time
err := got.UnmarshalCBOR(tc.in)
if err != nil {
if tc.errMessage == "" {
t.Fatalf("want nil error, got: %v", err)
} else if gotMessage := err.Error(); tc.errMessage != gotMessage {
t.Fatalf("want error: %q, got: %q", tc.errMessage, gotMessage)
}
} else if tc.errMessage != "" {
t.Fatalf("got nil error, want: %s", tc.errMessage)
}
if diff := cmp.Diff(tc.out, got); diff != "" {
t.Errorf("unexpected output:\n%s", diff)
}
})
}
}
func TestTimeProto(t *testing.T) {
cases := []struct {
input Time
@ -239,3 +301,27 @@ func TestTimeIsZero(t *testing.T) {
})
}
}
func TestTimeRoundtripCBOR(t *testing.T) {
fuzzer := randfill.New()
for i := 0; i < 500; i++ {
var initial, final Time
fuzzer.Fill(&initial)
b, err := cbor.Marshal(initial)
if err != nil {
t.Errorf("error encoding %v: %v", initial, err)
continue
}
err = cbor.Unmarshal(b, &final)
if err != nil {
t.Errorf("%v: error decoding %v: %v", initial, string(b), err)
}
if !final.Equal(&initial) {
diag, err := cbor.Diagnose(b)
if err != nil {
t.Logf("failed to produce diagnostic encoding of 0x%x: %v", b, err)
}
t.Errorf("expected equal: %v, %v (cbor was '%s')", initial, final, diag)
}
}
}

View File

@ -185,7 +185,7 @@ type ObjectMeta struct {
// Null for lists.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"`
CreationTimestamp Time `json:"creationTimestamp,omitempty,omitzero" protobuf:"bytes,8,opt,name=creationTimestamp"`
// DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
@ -236,6 +236,8 @@ type ObjectMeta struct {
// +optional
// +patchMergeKey=uid
// +patchStrategy=merge
// +listType=map
// +listMapKey=uid
OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"`
// Must be empty before the object is deleted from the registry. Each entry
@ -253,6 +255,7 @@ type ObjectMeta struct {
// are not vulnerable to ordering changes in the list.
// +optional
// +patchStrategy=merge
// +listType=set
Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"`
// Tombstone: ClusterName was a legacy field that was always cleared by
@ -268,6 +271,7 @@ type ObjectMeta struct {
// workflow used when modifying the object.
//
// +optional
// +listType=atomic
ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"`
}
@ -428,6 +432,15 @@ type ListOptions struct {
SendInitialEvents *bool `json:"sendInitialEvents,omitempty" protobuf:"varint,11,opt,name=sendInitialEvents"`
}
const (
// InitialEventsAnnotationKey the name of the key
// under which an annotation marking the end of
// a watchlist stream is stored.
//
// The annotation is added to a "Bookmark" event.
InitialEventsAnnotationKey = "k8s.io/initial-events-end"
)
// resourceVersionMatch specifies how the resourceVersion parameter is applied. resourceVersionMatch
// may only be set if resourceVersion is also set.
//
@ -531,7 +544,23 @@ type DeleteOptions struct {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,5,rep,name=dryRun"`
// if set to true, it will trigger an unsafe deletion of the resource in
// case the normal deletion flow fails with a corrupt object error.
// A resource is considered corrupt if it can not be retrieved from
// the underlying storage successfully because of a) its data can
// not be transformed e.g. decryption failure, or b) it fails
// to decode into an object.
// NOTE: unsafe deletion ignores finalizer constraints, skips
// precondition checks, and removes the object from the storage.
// WARNING: This may potentially break the cluster if the workload
// associated with the resource being unsafe-deleted relies on normal
// deletion flow. Use only if you REALLY know what you are doing.
// The default value is false, and the user must opt in to enable it
// +optional
IgnoreStoreReadErrorWithClusterBreakingPotential *bool `json:"ignoreStoreReadErrorWithClusterBreakingPotential,omitempty" protobuf:"varint,6,opt,name=ignoreStoreReadErrorWithClusterBreakingPotential"`
}
const (
@ -556,6 +585,7 @@ type CreateOptions struct {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
// +k8s:deprecated=includeUninitialized,protobuf=2
@ -600,6 +630,7 @@ type PatchOptions struct {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
// Force is going to "force" Apply requests. It means user will
@ -651,6 +682,7 @@ type ApplyOptions struct {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
// Force is going to "force" Apply requests. It means user will
@ -683,6 +715,7 @@ type UpdateOptions struct {
// request. Valid values are:
// - All: all dry run stages will be processed
// +optional
// +listType=atomic
DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
// fieldManager is a name associated with the actor or entity
@ -751,6 +784,7 @@ type Status struct {
// is not guaranteed to conform to any schema except that defined by
// the reason type.
// +optional
// +listType=atomic
Details *StatusDetails `json:"details,omitempty" protobuf:"bytes,5,opt,name=details"`
// Suggested HTTP return code for this status, 0 if not set.
// +optional
@ -784,6 +818,7 @@ type StatusDetails struct {
// The Causes array includes more details associated with the StatusReason
// failure. Not all StatusReasons may provide detailed causes.
// +optional
// +listType=atomic
Causes []StatusCause `json:"causes,omitempty" protobuf:"bytes,4,rep,name=causes"`
// If specified, the time in seconds before the operation should be retried. Some errors may indicate
// the client must take an alternate action - for those errors this field may indicate how long to wait
@ -882,6 +917,22 @@ const (
// Status code 500
StatusReasonServerTimeout StatusReason = "ServerTimeout"
// StatusReasonStoreReadError means that the server encountered an error while
// retrieving resources from the backend object store.
// This may be due to backend database error, or because processing of the read
// resource failed.
// Details:
// "kind" string - the kind attribute of the resource being acted on.
// "name" string - the prefix where the reading error(s) occurred
// "causes" []StatusCause
// - (optional):
// - "type" CauseType - CauseTypeUnexpectedServerResponse
// - "message" string - the error message from the store backend
// - "field" string - the full path with the key of the resource that failed reading
//
// Status code 500
StatusReasonStoreReadError StatusReason = "StorageReadError"
// StatusReasonTimeout means that the request could not be completed within the given time.
// Clients can get this response only when they specified a timeout param in the request,
// or if the server cannot complete the operation within a reasonable amount of time.
@ -1047,6 +1098,7 @@ type List struct {
type APIVersions struct {
TypeMeta `json:",inline"`
// versions are the api versions that are available.
// +listType=atomic
Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"`
// a map of client CIDR to server address that is serving this group.
// This is to help clients reach servers in the most network-efficient way possible.
@ -1055,6 +1107,7 @@ type APIVersions struct {
// The server returns only those CIDRs that it thinks that the client can match.
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
// +listType=atomic
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"`
}
@ -1065,6 +1118,7 @@ type APIVersions struct {
type APIGroupList struct {
TypeMeta `json:",inline"`
// groups is a list of APIGroup.
// +listType=atomic
Groups []APIGroup `json:"groups" protobuf:"bytes,1,rep,name=groups"`
}
@ -1077,6 +1131,7 @@ type APIGroup struct {
// name is the name of the group.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// versions are the versions supported in this group.
// +listType=atomic
Versions []GroupVersionForDiscovery `json:"versions" protobuf:"bytes,2,rep,name=versions"`
// preferredVersion is the version preferred by the API server, which
// probably is the storage version.
@ -1090,6 +1145,7 @@ type APIGroup struct {
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
// +optional
// +listType=atomic
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
}
@ -1134,8 +1190,10 @@ type APIResource struct {
// update, patch, delete, deletecollection, and proxy)
Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
// shortNames is a list of suggested short names of the resource.
// +listType=atomic
ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"`
// categories is a list of the grouped resources this resource belongs to (e.g. 'all')
// +listType=atomic
Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"`
// The hash value of the storage version, the version this resource is
// converted to when written to the data store. Value must be treated
@ -1168,6 +1226,7 @@ type APIResourceList struct {
// groupVersion is the group and version this APIResourceList is for.
GroupVersion string `json:"groupVersion" protobuf:"bytes,1,opt,name=groupVersion"`
// resources contains the name of the resources and if they are namespaced.
// +listType=atomic
APIResources []APIResource `json:"resources" protobuf:"bytes,2,rep,name=resources"`
}
@ -1175,6 +1234,7 @@ type APIResourceList struct {
// For example: "/healthz", "/apis".
type RootPaths struct {
// paths are the paths available at root.
// +listType=atomic
Paths []string `json:"paths" protobuf:"bytes,1,rep,name=paths"`
}
@ -1218,6 +1278,7 @@ type LabelSelector struct {
MatchLabels map[string]string `json:"matchLabels,omitempty" protobuf:"bytes,1,rep,name=matchLabels"`
// matchExpressions is a list of label selector requirements. The requirements are ANDed.
// +optional
// +listType=atomic
MatchExpressions []LabelSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,2,rep,name=matchExpressions"`
}
@ -1234,6 +1295,7 @@ type LabelSelectorRequirement struct {
// the values array must be empty. This array is replaced during a strategic
// merge patch.
// +optional
// +listType=atomic
Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"`
}
@ -1247,6 +1309,33 @@ const (
LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist"
)
// FieldSelectorRequirement is a selector that contains values, a key, and an operator that
// relates the key and values.
type FieldSelectorRequirement struct {
// key is the field selector key that the requirement applies to.
Key string `json:"key" protobuf:"bytes,1,opt,name=key"`
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists, DoesNotExist.
// The list of operators may grow in the future.
Operator FieldSelectorOperator `json:"operator" protobuf:"bytes,2,opt,name=operator,casttype=FieldSelectorOperator"`
// values is an array of string values.
// If the operator is In or NotIn, the values array must be non-empty.
// If the operator is Exists or DoesNotExist, the values array must be empty.
// +optional
// +listType=atomic
Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"`
}
// A field selector operator is the set of operators that can be used in a selector requirement.
type FieldSelectorOperator string
const (
FieldSelectorOpIn FieldSelectorOperator = "In"
FieldSelectorOpNotIn FieldSelectorOperator = "NotIn"
FieldSelectorOpExists FieldSelectorOperator = "Exists"
FieldSelectorOpDoesNotExist FieldSelectorOperator = "DoesNotExist"
)
// ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource
// that the fieldset applies to.
type ManagedFieldsEntry struct {
@ -1335,8 +1424,10 @@ type Table struct {
// columnDefinitions describes each column in the returned items array. The number of cells per row
// will always match the number of column definitions.
// +listType=atomic
ColumnDefinitions []TableColumnDefinition `json:"columnDefinitions"`
// rows is the list of items in the table.
// +listType=atomic
Rows []TableRow `json:"rows"`
}
@ -1369,12 +1460,14 @@ type TableRow struct {
// cells will be as wide as the column definitions array and may contain strings, numbers (float64 or
// int64), booleans, simple maps, lists, or null. See the type field of the column definition for a
// more detailed description.
// +listType=atomic
Cells []interface{} `json:"cells"`
// conditions describe additional status of a row that are relevant for a human user. These conditions
// apply to the row, not to the object, and will be specific to table output. The only defined
// condition type is 'Completed', for a row that indicates a resource that has run to completion and
// can be given less visual priority.
// +optional
// +listType=atomic
Conditions []TableRowCondition `json:"conditions,omitempty"`
// This field contains the requested additional information about each object based on the includeObject
// policy when requesting the Table. If "None", this field is empty, if "Object" this will be the

View File

@ -129,12 +129,24 @@ var map_DeleteOptions = map[string]string{
"orphanDependents": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.",
"propagationPolicy": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.",
"dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"ignoreStoreReadErrorWithClusterBreakingPotential": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it",
}
func (DeleteOptions) SwaggerDoc() map[string]string {
return map_DeleteOptions
}
var map_FieldSelectorRequirement = map[string]string{
"": "FieldSelectorRequirement is a selector that contains values, a key, and an operator that relates the key and values.",
"key": "key is the field selector key that the requirement applies to.",
"operator": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. The list of operators may grow in the future.",
"values": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.",
}
func (FieldSelectorRequirement) SwaggerDoc() map[string]string {
return map_FieldSelectorRequirement
}
var map_FieldsV1 = map[string]string{
"": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:<name>', where <name> is the name of a field in a struct, or key in a map 'v:<value>', where <value> is the exact json formatted value of a list item 'i:<index>', where <index> is position of a item in a list 'k:<keys>', where <keys> is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff",
}

View File

@ -20,6 +20,7 @@ import (
gojson "encoding/json"
"fmt"
"io"
"math/big"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -125,6 +126,29 @@ func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, err
return i, true, nil
}
// NestedNumberAsFloat64 returns the float64 value of a nested field. If the field's value is a
// float64, it is returned. If the field's value is an int64 that can be losslessly converted to
// float64, it will be converted and returned. Returns false if value is not found and an error if
// not a float64 or an int64 that can be accurately represented as a float64.
func NestedNumberAsFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) {
val, found, err := NestedFieldNoCopy(obj, fields...)
if !found || err != nil {
return 0, found, err
}
switch x := val.(type) {
case int64:
f, accuracy := big.NewInt(x).Float64()
if accuracy != big.Exact {
return 0, false, fmt.Errorf("%v accessor error: int64 value %v cannot be losslessly converted to float64", jsonPath(fields), x)
}
return f, true, nil
case float64:
return x, true, nil
default:
return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64 or int64", jsonPath(fields), val, val)
}
}
// NestedStringSlice returns a copy of []string value of a nested field.
// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice.
func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) {
@ -164,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
}
@ -179,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
}
@ -191,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)

View File

@ -18,6 +18,9 @@ package unstructured
import (
"io/ioutil"
"math"
"reflect"
"strings"
"sync"
"testing"
@ -178,7 +181,7 @@ func TestSetNestedStringSlice(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, obj["x"], 3)
assert.Len(t, obj["x"].(map[string]interface{})["z"], 1)
assert.Equal(t, obj["x"].(map[string]interface{})["z"].([]interface{})[0], "bar")
assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].([]interface{})[0])
}
func TestSetNestedSlice(t *testing.T) {
@ -193,7 +196,7 @@ func TestSetNestedSlice(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, obj["x"], 3)
assert.Len(t, obj["x"].(map[string]interface{})["z"], 1)
assert.Equal(t, obj["x"].(map[string]interface{})["z"].([]interface{})[0], "bar")
assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].([]interface{})[0])
}
func TestSetNestedStringMap(t *testing.T) {
@ -208,7 +211,7 @@ func TestSetNestedStringMap(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, obj["x"], 3)
assert.Len(t, obj["x"].(map[string]interface{})["z"], 1)
assert.Equal(t, obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"], "bar")
assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"])
}
func TestSetNestedMap(t *testing.T) {
@ -223,5 +226,163 @@ func TestSetNestedMap(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, obj["x"], 3)
assert.Len(t, obj["x"].(map[string]interface{})["z"], 1)
assert.Equal(t, obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"], "bar")
assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"])
}
func TestNestedNumberAsFloat64(t *testing.T) {
for _, tc := range []struct {
name string
obj map[string]interface{}
path []string
wantFloat64 float64
wantBool bool
wantErrMessage string
}{
{
name: "not found",
obj: nil,
path: []string{"missing"},
wantFloat64: 0,
wantBool: false,
wantErrMessage: "",
},
{
name: "found float64",
obj: map[string]interface{}{"value": float64(42)},
path: []string{"value"},
wantFloat64: 42,
wantBool: true,
wantErrMessage: "",
},
{
name: "found unexpected type bool",
obj: map[string]interface{}{"value": true},
path: []string{"value"},
wantFloat64: 0,
wantBool: false,
wantErrMessage: ".value accessor error: true is of the type bool, expected float64 or int64",
},
{
name: "found int64",
obj: map[string]interface{}{"value": int64(42)},
path: []string{"value"},
wantFloat64: 42,
wantBool: true,
wantErrMessage: "",
},
{
name: "found int64 not representable as float64",
obj: map[string]interface{}{"value": int64(math.MaxInt64)},
path: []string{"value"},
wantFloat64: 0,
wantBool: false,
wantErrMessage: ".value accessor error: int64 value 9223372036854775807 cannot be losslessly converted to float64",
},
} {
t.Run(tc.name, func(t *testing.T) {
gotFloat64, gotBool, gotErr := NestedNumberAsFloat64(tc.obj, tc.path...)
if gotFloat64 != tc.wantFloat64 {
t.Errorf("got %v, wanted %v", gotFloat64, tc.wantFloat64)
}
if gotBool != tc.wantBool {
t.Errorf("got %t, wanted %t", gotBool, tc.wantBool)
}
if tc.wantErrMessage != "" {
if gotErr == nil {
t.Errorf("got nil error, wanted %s", tc.wantErrMessage)
} else if gotErrMessage := gotErr.Error(); gotErrMessage != tc.wantErrMessage {
t.Errorf("wanted error %q, got: %v", gotErrMessage, tc.wantErrMessage)
}
} else if gotErr != nil {
t.Errorf("wanted nil error, got %v", gotErr)
}
})
}
}
func TestNestedNullCoercingStringMap(t *testing.T) {
for _, tc := range []struct {
name string
obj map[string]interface{}
path []string
wantObj map[string]string
wantFound bool
wantErrMessage string
}{
{
name: "missing map",
obj: nil,
path: []string{"path"},
wantObj: nil,
wantFound: false,
wantErrMessage: "",
},
{
name: "null map",
obj: map[string]interface{}{"path": nil},
path: []string{"path"},
wantObj: nil,
wantFound: true,
wantErrMessage: "",
},
{
name: "non map",
obj: map[string]interface{}{"path": 0},
path: []string{"path"},
wantObj: nil,
wantFound: false,
wantErrMessage: "type int",
},
{
name: "empty map",
obj: map[string]interface{}{"path": map[string]interface{}{}},
path: []string{"path"},
wantObj: map[string]string{},
wantFound: true,
wantErrMessage: "",
},
{
name: "string value",
obj: map[string]interface{}{"path": map[string]interface{}{"a": "1", "b": "2"}},
path: []string{"path"},
wantObj: map[string]string{"a": "1", "b": "2"},
wantFound: true,
wantErrMessage: "",
},
{
name: "null value",
obj: map[string]interface{}{"path": map[string]interface{}{"a": "1", "b": nil}},
path: []string{"path"},
wantObj: map[string]string{"a": "1", "b": ""},
wantFound: true,
wantErrMessage: "",
},
{
name: "invalid value",
obj: map[string]interface{}{"path": map[string]interface{}{"a": "1", "b": nil, "c": 0}},
path: []string{"path"},
wantObj: nil,
wantFound: false,
wantErrMessage: `key "c": 0`,
},
} {
t.Run(tc.name, func(t *testing.T) {
gotObj, gotFound, gotErr := NestedNullCoercingStringMap(tc.obj, tc.path...)
if !reflect.DeepEqual(gotObj, tc.wantObj) {
t.Errorf("got %#v, wanted %#v", gotObj, tc.wantObj)
}
if gotFound != tc.wantFound {
t.Errorf("got %v, wanted %v", gotFound, tc.wantFound)
}
if tc.wantErrMessage != "" {
if gotErr == nil {
t.Errorf("got nil error, wanted %s", tc.wantErrMessage)
} else if gotErrMessage := gotErr.Error(); !strings.Contains(gotErrMessage, tc.wantErrMessage) {
t.Errorf("wanted error %q, got: %v", gotErrMessage, tc.wantErrMessage)
}
} else if gotErr != nil {
t.Errorf("wanted nil error, got %v", gotErr)
}
})
}
}

View File

@ -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
}
@ -450,10 +450,14 @@ func (u *Unstructured) SetFinalizers(finalizers []string) {
}
func (u *Unstructured) GetManagedFields() []metav1.ManagedFieldsEntry {
items, found, err := NestedSlice(u.Object, "metadata", "managedFields")
v, found, err := NestedFieldNoCopy(u.Object, "metadata", "managedFields")
if !found || err != nil {
return nil
}
items, ok := v.([]interface{})
if !ok {
return nil
}
managedFields := []metav1.ManagedFieldsEntry{}
for _, item := range items {
m, ok := item.(map[string]interface{})

View File

@ -55,11 +55,9 @@ func TestObjectToUnstructuredConversion(t *testing.T) {
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
},
"spec": map[string]interface{}{},
"status": map[string]interface{}{},
"metadata": map[string]interface{}{},
"spec": map[string]interface{}{},
"status": map[string]interface{}{},
},
},
},
@ -78,8 +76,7 @@ func TestObjectToUnstructuredConversion(t *testing.T) {
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",
@ -155,8 +152,7 @@ func TestUnstructuredToObjectConversion(t *testing.T) {
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",
@ -181,8 +177,7 @@ func TestUnstructuredToObjectConversion(t *testing.T) {
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",
@ -207,8 +202,7 @@ func TestUnstructuredToObjectConversion(t *testing.T) {
"apiVersion": "v9",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",
@ -232,8 +226,7 @@ func TestUnstructuredToObjectConversion(t *testing.T) {
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",
@ -358,8 +351,7 @@ func TestUnstructuredToGVConversion(t *testing.T) {
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",
@ -388,8 +380,7 @@ func TestUnstructuredToGVConversion(t *testing.T) {
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",
@ -414,8 +405,7 @@ func TestUnstructuredToGVConversion(t *testing.T) {
"apiVersion": "v1",
"kind": "Carp",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "noxu",
"name": "noxu",
},
"spec": map[string]interface{}{
"hostname": "example.com",

View File

@ -17,12 +17,20 @@ limitations under the License.
package unstructured_test
import (
"bytes"
"math/big"
"math/rand"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/randfill"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
"k8s.io/apimachinery/pkg/api/equality"
metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
@ -30,6 +38,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
cborserializer "k8s.io/apimachinery/pkg/runtime/serializer/cbor"
jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
)
func TestNilUnstructuredContent(t *testing.T) {
@ -57,7 +67,7 @@ func TestUnstructuredMetadataRoundTrip(t *testing.T) {
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
uCopy := u.DeepCopy()
metadata := &metav1.ObjectMeta{}
fuzzer.Fuzz(metadata)
fuzzer.Fill(metadata)
if err := setObjectMeta(u, metadata); err != nil {
t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err)
@ -84,7 +94,7 @@ func TestUnstructuredMetadataOmitempty(t *testing.T) {
// fuzz to make sure we don't miss any function calls below
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
metadata := &metav1.ObjectMeta{}
fuzzer.Fuzz(metadata)
fuzzer.Fill(metadata)
if err := setObjectMeta(u, metadata); err != nil {
t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err)
}
@ -118,6 +128,18 @@ func TestUnstructuredMetadataOmitempty(t *testing.T) {
}
}
// TestRoundTripJSONCBORUnstructured performs fuzz testing for roundtrip for
// unstructured object between JSON and CBOR
func TestRoundTripJSONCBORUnstructured(t *testing.T) {
roundtripType[*unstructured.Unstructured](t)
}
// TestRoundTripJSONCBORUnstructuredList performs fuzz testing for roundtrip for
// unstructuredList object between JSON and CBOR
func TestRoundTripJSONCBORUnstructuredList(t *testing.T) {
roundtripType[*unstructured.UnstructuredList](t)
}
func setObjectMeta(u *unstructured.Unstructured, objectMeta *metav1.ObjectMeta) error {
if objectMeta == nil {
unstructured.RemoveNestedField(u.UnstructuredContent(), "metadata")
@ -148,3 +170,307 @@ func setObjectMetaUsingAccessors(u, uCopy *unstructured.Unstructured) {
uCopy.SetFinalizers(u.GetFinalizers())
uCopy.SetManagedFields(u.GetManagedFields())
}
// roundtripType performs fuzz testing for roundtrip conversion for
// unstructured or unstructuredList object between two formats (A and B) in forward
// and backward directions
// Original and final unstructured/list are compared along with all intermediate ones
func roundtripType[U runtime.Unstructured](t *testing.T) {
scheme := runtime.NewScheme()
fuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, unstructuredFuzzerFuncs), rand.NewSource(getSeed(t)), serializer.NewCodecFactory(scheme))
jS := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, scheme, scheme, jsonserializer.SerializerOptions{})
cS := cborserializer.NewSerializer(scheme, scheme)
for i := 0; i < 50; i++ {
original := reflect.New(reflect.TypeFor[U]().Elem()).Interface().(runtime.Unstructured)
fuzzer.Fill(original)
// unstructured -> JSON > unstructured > CBOR -> unstructured -> JSON -> unstructured
roundtrip(t, original, jS, cS)
// unstructured -> CBOR > unstructured > JSON -> unstructured -> CBOR -> unstructured
roundtrip(t, original, cS, jS)
}
}
// roundtrip tests that an Unstructured object roundtrips faithfully along the
// sequence Unstructured -> A -> Unstructured -> B -> Unstructured -> A -> Unstructured,
// given serializers for two encodings A and B. The final object and both intermediate
// objects must all be equal to the original.
func roundtrip(t *testing.T, original runtime.Unstructured, a, b runtime.Serializer) {
var buf bytes.Buffer
buf.Reset()
// (original) Unstructured -> A
if err := a.Encode(original, &buf); err != nil {
t.Fatalf("error encoding original unstructured to A: %v", err)
}
// A -> intermediate unstructured
uA := reflect.New(reflect.TypeOf(original).Elem()).Interface().(runtime.Object)
uA, _, err := a.Decode(buf.Bytes(), nil, uA)
if err != nil {
t.Fatalf("error decoding A to unstructured: %v", err)
}
// Compare original unstructured vs intermediate unstructured
tmp, ok := uA.(runtime.Unstructured)
if !ok {
t.Fatalf("unexpected type %T for unstructured", tmp)
}
if !unstructuredEqual(t, original, uA.(runtime.Unstructured)) {
t.Fatalf("original unstructured differed from unstructured via A: %v", cmp.Diff(original, uA))
}
buf.Reset()
// intermediate unstructured -> B
if err := b.Encode(uA, &buf); err != nil {
t.Fatalf("error encoding unstructured to B: %v", err)
}
// B -> intermediate unstructured
uB := reflect.New(reflect.TypeOf(original).Elem()).Interface().(runtime.Object)
uB, _, err = b.Decode(buf.Bytes(), nil, uB)
if err != nil {
t.Fatalf("error decoding B to unstructured: %v", err)
}
// compare original vs intermediate unstructured
tmp, ok = uB.(runtime.Unstructured)
if !ok {
t.Fatalf("unexpected type %T for unstructured", tmp)
}
if !unstructuredEqual(t, original, uB.(runtime.Unstructured)) {
t.Fatalf("unstructured via A differed from unstructured via B: %v", cmp.Diff(original, uB))
}
// intermediate unstructured -> A
buf.Reset()
if err := a.Encode(uB, &buf); err != nil {
t.Fatalf("error encoding unstructured to A: %v", err)
}
// A -> final unstructured
final := reflect.New(reflect.TypeOf(original).Elem()).Interface().(runtime.Object)
final, _, err = a.Decode(buf.Bytes(), nil, final)
if err != nil {
t.Fatalf("error decoding A to unstructured: %v", err)
}
// Compare original unstructured vs final unstructured
tmp, ok = final.(runtime.Unstructured)
if !ok {
t.Fatalf("unexpected type %T for unstructured", tmp)
}
if !unstructuredEqual(t, original, final.(runtime.Unstructured)) {
t.Errorf("object changed during unstructured->A->unstructured->B->unstructured roundtrip, diff: %s", cmp.Diff(original, final))
}
}
func getSeed(t *testing.T) int64 {
seed := int64(time.Now().Nanosecond())
if override := os.Getenv("TEST_RAND_SEED"); len(override) > 0 {
overrideSeed, err := strconv.ParseInt(override, 10, 64)
if err != nil {
t.Fatal(err)
}
seed = overrideSeed
t.Logf("using overridden seed: %d", seed)
} else {
t.Logf("seed (override with TEST_RAND_SEED if desired): %d", seed)
}
return seed
}
const (
maxUnstructuredDepth = 64
maxUnstructuredFanOut = 5
)
func unstructuredFuzzerFuncs(codecs serializer.CodecFactory) []interface{} {
return []interface{}{
func(u *unstructured.Unstructured, c randfill.Continue) {
obj := make(map[string]interface{})
obj["apiVersion"] = generateValidAPIVersionString(c)
obj["kind"] = generateNonEmptyString(c)
for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- {
obj[c.String(0)] = generateRandomTypeValue(maxUnstructuredDepth, c)
}
u.Object = obj
},
func(ul *unstructured.UnstructuredList, c randfill.Continue) {
obj := make(map[string]interface{})
obj["apiVersion"] = generateValidAPIVersionString(c)
obj["kind"] = generateNonEmptyString(c)
for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- {
obj[c.String(0)] = generateRandomTypeValue(maxUnstructuredDepth, c)
}
for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- {
var item = unstructured.Unstructured{}
c.Fill(&item)
ul.Items = append(ul.Items, item)
}
ul.Object = obj
},
}
}
func generateNonEmptyString(c randfill.Continue) string {
temp := c.String(0)
for len(temp) == 0 {
temp = c.String(0)
}
return temp
}
// generateNonEmptyNoSlashString generates a non-empty string without any slashes
func generateNonEmptyNoSlashString(c randfill.Continue) string {
temp := strings.ReplaceAll(generateNonEmptyString(c), "/", "")
for len(temp) == 0 {
temp = strings.ReplaceAll(generateNonEmptyString(c), "/", "")
}
return temp
}
// generateValidAPIVersionString generates valid apiVersion string with formats:
// <string>/<string> or <string>
func generateValidAPIVersionString(c randfill.Continue) string {
if c.Bool() {
return generateNonEmptyNoSlashString(c) + "/" + generateNonEmptyNoSlashString(c)
} else {
return generateNonEmptyNoSlashString(c)
}
}
// generateRandomTypeValue generates fuzzed valid JSON data types:
// 1. numbers (float64, int64)
// 2. string (utf-8 encodings)
// 3. boolean
// 4. array ([]interface{})
// 5. object (map[string]interface{})
// 6. null
// Decoding into unstructured can only produce a nil interface{} value or the
// concrete types map[string]interface{}, []interface{}, int64, float64, string, and bool
// If a value of other types is put into an unstructured, it will roundtrip
// to one of the above list of supported types. For example, if Time type is used,
// it will be encoded into a RFC 3339 format string such as "2001-02-03T12:34:56Z"
// and when decoding into Unstructured, there is no information to indicate
// that this string was originally produced by encoding a metav1.Time.
// All external-versioned builtin types are exercised through RoundtripToUnstructured
// in apitesting package. Types like metav1.Time are implicitly being exercised
// because they appear as fields in those types.
func generateRandomTypeValue(depth int, c randfill.Continue) interface{} {
t := c.Rand.Intn(120)
// If the max depth for unstructured is reached, only add non-recursive types
// which is 20+ in range
if depth == 0 {
t = 20 + c.Rand.Intn(120-20)
}
switch {
case t < 10:
item := make([]interface{}, c.Intn(maxUnstructuredFanOut))
for k := range item {
item[k] = generateRandomTypeValue(depth-1, c)
}
return item
case t < 20:
item := map[string]interface{}{}
for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- {
item[c.String(0)] = generateRandomTypeValue(depth-1, c)
}
return item
case t < 40:
// Only valid UTF-8 encodings
var item string
c.Fill(&item)
return item
case t < 60:
var item int64
c.Fill(&item)
return item
case t < 80:
var item bool
c.Fill(&item)
return item
case t < 100:
return c.Rand.NormFloat64()
case t < 120:
return nil
default:
panic("invalid case")
}
}
func unstructuredEqual(t *testing.T, a, b runtime.Unstructured) bool {
return anyEqual(t, a.UnstructuredContent(), b.UnstructuredContent())
}
// numberEqual asserts equality of two numbers which one is int64 and one is float64
// In JSON, a non-decimal float64 is converted to int64 automatically in case the
// float64 fits into int64 range. Otherwise, the non-decimal float64 remains a float.
// As a result, this func does an int64 to float64 conversion using math/big package
// to ensure the conversion is lossless before comparison.
func numberEqual(a int64, b float64) bool {
// Ensure roundtrip int64 to float64 conversion is lossless
f, accuracy := big.NewInt(a).Float64()
if accuracy == big.Exact {
// Distinction between int64 and float64 is not preserved during JSON roundtrip for all numbers.
return f == b
}
return false
}
func anyEqual(t *testing.T, a, b interface{}) bool {
switch b.(type) {
case nil, bool, string, int64, float64, []interface{}, map[string]interface{}:
default:
t.Fatalf("unexpected value %v of type %T", b, b)
}
switch ac := a.(type) {
case nil, bool, string:
return ac == b
case int64:
if bc, ok := b.(float64); ok {
return numberEqual(ac, bc)
}
return ac == b
case float64:
if bc, ok := b.(int64); ok {
return numberEqual(bc, ac)
}
return ac == b
case []interface{}:
bc, ok := b.([]interface{})
if !ok {
return false
}
if len(ac) != len(bc) {
return false
}
for i, aa := range ac {
if !anyEqual(t, aa, bc[i]) {
return false
}
}
return true
case map[string]interface{}:
bc, ok := b.(map[string]interface{})
if !ok {
return false
}
if len(ac) != len(bc) {
return false
}
for k, aa := range ac {
bb, ok := bc[k]
if !ok {
return false
}
if !anyEqual(t, aa, bb) {
return false
}
}
return true
default:
t.Fatalf("unexpected value %v of type %T", a, a)
}
return true
}

View File

@ -50,11 +50,11 @@ func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.Serial
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, true),
Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{}),
PrettySerializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{Pretty: true}),
StreamSerializer: &runtime.StreamSerializerInfo{
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false),
Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{}),
Framer: json.Framer,
},
},
@ -63,7 +63,7 @@ func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.Serial
MediaTypeType: "application",
MediaTypeSubType: "yaml",
EncodesAsText: true,
Serializer: json.NewYAMLSerializer(json.DefaultMetaFactory, s.creator, s.typer),
Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{Yaml: true}),
},
}
}

View File

@ -26,12 +26,18 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
// LabelSelectorValidationOptions is a struct that can be passed to ValidateLabelSelector to record the validate options
type LabelSelectorValidationOptions struct {
// Allow invalid label value in selector
AllowInvalidLabelValueInSelector bool
// Allows an operator that is not interpretable to pass validation. This is useful for cases where a broader check
// can be performed, as in a *SubjectAccessReview
AllowUnknownOperatorInRequirement bool
}
// LabelSelectorHasInvalidLabelValue returns true if the given selector contains an invalid label value in a match expression.
@ -79,7 +85,9 @@ func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts L
allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
}
default:
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
if !opts.AllowUnknownOperatorInRequirement {
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
}
}
allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
if !opts.AllowInvalidLabelValueInSelector {
@ -96,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
}
@ -113,6 +121,39 @@ func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorLi
return allErrs
}
// FieldSelectorValidationOptions is a struct that can be passed to ValidateFieldSelectorRequirement to record the validate options
type FieldSelectorValidationOptions struct {
// Allows an operator that is not interpretable to pass validation. This is useful for cases where a broader check
// can be performed, as in a *SubjectAccessReview
AllowUnknownOperatorInRequirement bool
}
// ValidateLabelSelectorRequirement validates the requirement according to the opts and returns any validation errors.
func ValidateFieldSelectorRequirement(requirement metav1.FieldSelectorRequirement, opts FieldSelectorValidationOptions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(requirement.Key) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("key"), "must be specified"))
}
switch requirement.Operator {
case metav1.FieldSelectorOpIn, metav1.FieldSelectorOpNotIn:
if len(requirement.Values) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'"))
}
case metav1.FieldSelectorOpExists, metav1.FieldSelectorOpDoesNotExist:
if len(requirement.Values) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
}
default:
if !opts.AllowUnknownOperatorInRequirement {
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), requirement.Operator, "not a valid selector operator"))
}
}
return allErrs
}
func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList {
allErrs := field.ErrorList{}
//lint:file-ignore SA1019 Keep validation for deprecated OrphanDependents option until it's being removed
@ -126,6 +167,7 @@ func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList {
allErrs = append(allErrs, field.NotSupported(field.NewPath("propagationPolicy"), options.PropagationPolicy, []string{string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground), string(metav1.DeletePropagationOrphan), "nil"}))
}
allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
allErrs = append(allErrs, ValidateIgnoreStoreReadError(field.NewPath("ignoreStoreReadErrorWithClusterBreakingPotential"), options)...)
return allErrs
}
@ -147,15 +189,16 @@ func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList {
func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList {
allErrs := field.ErrorList{}
if patchType != types.ApplyPatchType {
if options.Force != nil {
allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
}
} else {
switch patchType {
case types.ApplyYAMLPatchType, types.ApplyCBORPatchType:
if options.FieldManager == "" {
// This field is defaulted to "kubectl" by kubectl, but HAS TO be explicitly set by controllers.
allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch"))
}
default:
if options.Force != nil {
allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
}
}
allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
@ -173,7 +216,7 @@ func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorL
// considered as not set and is defaulted by the rest of the process
// (unless apply is used, in which case it is required).
if len(fieldManager) > FieldManagerMaxLength {
allErrs = append(allErrs, field.TooLong(fldPath, fieldManager, FieldManagerMaxLength))
allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, FieldManagerMaxLength))
}
// Verify that all characters are printable.
for i, r := range fieldManager {
@ -238,7 +281,7 @@ func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *fiel
allErrs = append(allErrs, ValidateFieldManager(fields.Manager, fldPath.Child("manager"))...)
if len(fields.Subresource) > MaxSubresourceNameLength {
allErrs = append(allErrs, field.TooLong(fldPath.Child("subresource"), fields.Subresource, MaxSubresourceNameLength))
allErrs = append(allErrs, field.TooLong(fldPath.Child("subresource"), "" /*unused*/, MaxSubresourceNameLength))
}
}
return allErrs
@ -285,22 +328,22 @@ func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.Er
}
if condition.LastTransitionTime.IsZero() {
allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set"))
allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), ""))
}
if len(condition.Reason) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set"))
allErrs = append(allErrs, field.Required(fldPath.Child("reason"), ""))
} else {
for _, currErr := range isValidConditionReason(condition.Reason) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr))
}
if len(condition.Reason) > maxReasonLen {
allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), condition.Reason, maxReasonLen))
allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), "" /*unused*/, maxReasonLen))
}
}
if len(condition.Message) > maxMessageLen {
allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, maxMessageLen))
allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), "" /*unused*/, maxMessageLen))
}
return allErrs
@ -318,3 +361,31 @@ func isValidConditionReason(value string) []string {
}
return nil
}
// ValidateIgnoreStoreReadError validates that delete options are valid when
// ignoreStoreReadErrorWithClusterBreakingPotential is enabled
func ValidateIgnoreStoreReadError(fldPath *field.Path, options *metav1.DeleteOptions) field.ErrorList {
allErrs := field.ErrorList{}
if enabled := ptr.Deref[bool](options.IgnoreStoreReadErrorWithClusterBreakingPotential, false); !enabled {
return allErrs
}
if len(options.DryRun) > 0 {
allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .dryRun"))
}
if options.PropagationPolicy != nil {
allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .propagationPolicy"))
}
//nolint:staticcheck // Keep validation for deprecated OrphanDependents option until it's being removed
if options.OrphanDependents != nil {
allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .orphanDependents"))
}
if options.GracePeriodSeconds != nil {
allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .gracePeriodSeconds"))
}
if options.Preconditions != nil {
allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .preconditions"))
}
return allErrs
}

View File

@ -21,9 +21,13 @@ import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
func TestValidateLabels(t *testing.T) {
@ -128,8 +132,94 @@ func TestInvalidDryRun(t *testing.T) {
}
func boolPtr(b bool) *bool {
return &b
func TestValidateDeleteOptionsWithIgnoreStoreReadError(t *testing.T) {
fieldPath := field.NewPath("ignoreStoreReadErrorWithClusterBreakingPotential")
tests := []struct {
name string
opts metav1.DeleteOptions
expectedErrors field.ErrorList
}{
{
name: "option is nil",
opts: metav1.DeleteOptions{
IgnoreStoreReadErrorWithClusterBreakingPotential: nil,
DryRun: []string{"All"},
},
expectedErrors: field.ErrorList{},
},
{
name: "option is false, PropagationPolicy is set",
opts: metav1.DeleteOptions{
IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](false),
DryRun: []string{"All"},
PropagationPolicy: ptr.To[metav1.DeletionPropagation](metav1.DeletePropagationBackground),
GracePeriodSeconds: ptr.To[int64](0),
Preconditions: &metav1.Preconditions{},
},
expectedErrors: field.ErrorList{},
},
{
name: "option is false, OrphanDependents is set",
opts: metav1.DeleteOptions{
IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](false),
DryRun: []string{"All"},
//nolint:staticcheck // until it's being removed
OrphanDependents: ptr.To[bool](true),
GracePeriodSeconds: ptr.To[int64](0),
Preconditions: &metav1.Preconditions{},
},
expectedErrors: field.ErrorList{},
},
{
name: "option is true, PropagationPolicy is set",
opts: metav1.DeleteOptions{
IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](true),
DryRun: []string{"All"},
PropagationPolicy: ptr.To[metav1.DeletionPropagation](metav1.DeletePropagationBackground),
GracePeriodSeconds: ptr.To[int64](0),
Preconditions: &metav1.Preconditions{},
},
expectedErrors: field.ErrorList{
field.Invalid(fieldPath, true, "cannot be set together with .dryRun"),
field.Invalid(fieldPath, true, "cannot be set together with .propagationPolicy"),
field.Invalid(fieldPath, true, "cannot be set together with .gracePeriodSeconds"),
field.Invalid(fieldPath, true, "cannot be set together with .preconditions"),
},
},
{
name: "option is true, OrphanDependents is set",
opts: metav1.DeleteOptions{
IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](true),
DryRun: []string{"All"},
//nolint:staticcheck // until it's being removed
OrphanDependents: ptr.To[bool](true),
GracePeriodSeconds: ptr.To[int64](0),
Preconditions: &metav1.Preconditions{},
},
expectedErrors: field.ErrorList{
field.Invalid(fieldPath, true, "cannot be set together with .dryRun"),
field.Invalid(fieldPath, true, "cannot be set together with .orphanDependents"),
field.Invalid(fieldPath, true, "cannot be set together with .gracePeriodSeconds"),
field.Invalid(fieldPath, true, "cannot be set together with .preconditions"),
},
},
{
name: "option is true, no other option is set",
opts: metav1.DeleteOptions{
IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](false),
},
expectedErrors: field.ErrorList{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
errGot := ValidateDeleteOptions(&test.opts)
if !cmp.Equal(test.expectedErrors, errGot) {
t.Errorf("expected error(s) to match, diff: %s", cmp.Diff(test.expectedErrors, errGot))
}
})
}
}
func TestValidPatchOptions(t *testing.T) {
@ -138,15 +228,26 @@ func TestValidPatchOptions(t *testing.T) {
patchType types.PatchType
}{{
opts: metav1.PatchOptions{
Force: boolPtr(true),
Force: ptr.To(true),
FieldManager: "kubectl",
},
patchType: types.ApplyPatchType,
patchType: types.ApplyYAMLPatchType,
}, {
opts: metav1.PatchOptions{
FieldManager: "kubectl",
},
patchType: types.ApplyPatchType,
patchType: types.ApplyYAMLPatchType,
}, {
opts: metav1.PatchOptions{
Force: ptr.To(true),
FieldManager: "kubectl",
},
patchType: types.ApplyCBORPatchType,
}, {
opts: metav1.PatchOptions{
FieldManager: "kubectl",
},
patchType: types.ApplyCBORPatchType,
}, {
opts: metav1.PatchOptions{},
patchType: types.MergePatchType,
@ -175,12 +276,17 @@ func TestInvalidPatchOptions(t *testing.T) {
// missing manager
{
opts: metav1.PatchOptions{},
patchType: types.ApplyPatchType,
patchType: types.ApplyYAMLPatchType,
},
// missing manager
{
opts: metav1.PatchOptions{},
patchType: types.ApplyCBORPatchType,
},
// force on non-apply
{
opts: metav1.PatchOptions{
Force: boolPtr(true),
Force: ptr.To(true),
},
patchType: types.MergePatchType,
},
@ -188,7 +294,7 @@ func TestInvalidPatchOptions(t *testing.T) {
{
opts: metav1.PatchOptions{
FieldManager: "kubectl",
Force: boolPtr(false),
Force: ptr.To(false),
},
patchType: types.MergePatchType,
},
@ -332,7 +438,7 @@ func TestValidateConditions(t *testing.T) {
if !hasError(errs, needle) {
t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
}
needle = `status.conditions[0].lastTransitionTime: Required value: must be set`
needle = `status.conditions[0].lastTransitionTime: Required value`
if !hasError(errs, needle) {
t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
}
@ -470,7 +576,7 @@ func TestLabelSelectorMatchExpression(t *testing.T) {
}}
for index, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{false}, field.NewPath("labelSelector"))
allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, field.NewPath("labelSelector"))
if len(allErrs) != testCase.wantErrorNumber {
t.Errorf("case[%d]: expected failure", index)
}

View File

@ -339,6 +339,13 @@ func autoConvert_url_Values_To_v1_DeleteOptions(in *url.Values, out *DeleteOptio
} else {
out.DryRun = nil
}
if values, ok := map[string][]string(*in)["ignoreStoreReadErrorWithClusterBreakingPotential"]; ok && len(values) > 0 {
if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.IgnoreStoreReadErrorWithClusterBreakingPotential, s); err != nil {
return err
}
} else {
out.IgnoreStoreReadErrorWithClusterBreakingPotential = nil
}
return nil
}

View File

@ -290,6 +290,11 @@ func (in *DeleteOptions) DeepCopyInto(out *DeleteOptions) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.IgnoreStoreReadErrorWithClusterBreakingPotential != nil {
in, out := &in.IgnoreStoreReadErrorWithClusterBreakingPotential, &out.IgnoreStoreReadErrorWithClusterBreakingPotential
*out = new(bool)
**out = **in
}
return
}
@ -327,6 +332,27 @@ func (in *Duration) DeepCopy() *Duration {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FieldSelectorRequirement) DeepCopyInto(out *FieldSelectorRequirement) {
*out = *in
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FieldSelectorRequirement.
func (in *FieldSelectorRequirement) DeepCopy() *FieldSelectorRequirement {
if in == nil {
return nil
}
out := new(FieldSelectorRequirement)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FieldsV1) DeepCopyInto(out *FieldsV1) {
*out = *in

View File

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

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto
// source: k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto
package v1beta1
@ -47,7 +47,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
func (m *PartialObjectMetadataList) Reset() { *m = PartialObjectMetadataList{} }
func (*PartialObjectMetadataList) ProtoMessage() {}
func (*PartialObjectMetadataList) Descriptor() ([]byte, []int) {
return fileDescriptor_90ec10f86b91f9a8, []int{0}
return fileDescriptor_39237a8d8061b52f, []int{0}
}
func (m *PartialObjectMetadataList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -77,31 +77,30 @@ func init() {
}
func init() {
proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto", fileDescriptor_90ec10f86b91f9a8)
proto.RegisterFile("k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto", fileDescriptor_39237a8d8061b52f)
}
var fileDescriptor_90ec10f86b91f9a8 = []byte{
// 317 bytes of a gzipped FileDescriptorProto
var fileDescriptor_39237a8d8061b52f = []byte{
// 303 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x4b, 0xf3, 0x30,
0x1c, 0xc6, 0x9b, 0xf7, 0x65, 0x30, 0x3a, 0x04, 0xd9, 0x69, 0xee, 0x90, 0x0d, 0x4f, 0xdb, 0xc1,
0x84, 0x0d, 0x11, 0xc1, 0xdb, 0x6e, 0x82, 0x32, 0xd9, 0x51, 0x3c, 0x98, 0x76, 0x7f, 0xbb, 0x58,
0xd3, 0x94, 0xe4, 0xdf, 0x81, 0x37, 0x3f, 0x82, 0x1f, 0x6b, 0xc7, 0x1d, 0x07, 0xc2, 0x70, 0xf5,
0x8b, 0x48, 0xda, 0x2a, 0x32, 0x14, 0x7a, 0xeb, 0xf3, 0x94, 0xdf, 0x2f, 0x4f, 0x20, 0xfe, 0x2c,
0x3e, 0xb7, 0x4c, 0x6a, 0x1e, 0x67, 0x01, 0x98, 0x04, 0x10, 0x2c, 0x5f, 0x42, 0x32, 0xd7, 0x86,
0x57, 0x3f, 0x44, 0x2a, 0x95, 0x08, 0x17, 0x32, 0x01, 0xf3, 0xcc, 0xd3, 0x38, 0x72, 0x85, 0xe5,
0x0a, 0x50, 0xf0, 0xe5, 0x28, 0x00, 0x14, 0x23, 0x1e, 0x41, 0x02, 0x46, 0x20, 0xcc, 0x59, 0x6a,
0x34, 0xea, 0xf6, 0xb0, 0x44, 0xd9, 0x4f, 0x94, 0xa5, 0x71, 0xe4, 0x0a, 0xcb, 0x1c, 0xca, 0x2a,
0xb4, 0x7b, 0x12, 0x49, 0x5c, 0x64, 0x01, 0x0b, 0xb5, 0xe2, 0x91, 0x8e, 0x34, 0x2f, 0x0c, 0x41,
0xf6, 0x50, 0xa4, 0x22, 0x14, 0x5f, 0xa5, 0xb9, 0x7b, 0x5a, 0x67, 0xd4, 0xfe, 0x9e, 0xee, 0xd9,
0x5f, 0x94, 0xc9, 0x12, 0x94, 0x0a, 0xb8, 0x0d, 0x17, 0xa0, 0xc4, 0x3e, 0x77, 0xfc, 0x46, 0xfc,
0xa3, 0x1b, 0x61, 0x50, 0x8a, 0xa7, 0x69, 0xf0, 0x08, 0x21, 0x5e, 0x03, 0x8a, 0xb9, 0x40, 0x71,
0x25, 0x2d, 0xb6, 0xef, 0xfc, 0xa6, 0xaa, 0x72, 0xe7, 0x5f, 0x9f, 0x0c, 0x5a, 0x63, 0xc6, 0xea,
0x5c, 0x9c, 0x39, 0xda, 0x99, 0x26, 0x87, 0xab, 0x6d, 0xcf, 0xcb, 0xb7, 0xbd, 0xe6, 0x57, 0x33,
0xfb, 0x36, 0xb6, 0xef, 0xfd, 0x86, 0x44, 0x50, 0xb6, 0x43, 0xfa, 0xff, 0x07, 0xad, 0xf1, 0x45,
0x3d, 0xf5, 0xaf, 0x6b, 0x27, 0x07, 0xd5, 0x39, 0x8d, 0x4b, 0x67, 0x9c, 0x95, 0xe2, 0xc9, 0x74,
0xb5, 0xa3, 0xde, 0x7a, 0x47, 0xbd, 0xcd, 0x8e, 0x7a, 0x2f, 0x39, 0x25, 0xab, 0x9c, 0x92, 0x75,
0x4e, 0xc9, 0x26, 0xa7, 0xe4, 0x3d, 0xa7, 0xe4, 0xf5, 0x83, 0x7a, 0xb7, 0xc3, 0xda, 0xcf, 0xe0,
0x33, 0x00, 0x00, 0xff, 0xff, 0x30, 0x97, 0x8b, 0x11, 0x4b, 0x02, 0x00, 0x00,
0x84, 0x0d, 0x11, 0xc5, 0xdb, 0x6e, 0x82, 0x32, 0xd9, 0x51, 0x3c, 0x98, 0x76, 0x31, 0x8b, 0x35,
0x4d, 0x69, 0xfe, 0x15, 0xbc, 0xf9, 0x11, 0xfc, 0x58, 0x3d, 0xee, 0x38, 0x10, 0x86, 0x8d, 0x5f,
0x44, 0xd2, 0x56, 0x91, 0xa1, 0xd0, 0x5b, 0x9e, 0x07, 0x7e, 0xbf, 0x3c, 0x81, 0xf8, 0x67, 0xd1,
0xa9, 0x21, 0x52, 0x53, 0x96, 0x48, 0xc5, 0xc2, 0x95, 0x8c, 0x79, 0xfa, 0x4c, 0x93, 0x48, 0xb8,
0xc2, 0x50, 0xc5, 0x81, 0xd1, 0xa7, 0x49, 0xc0, 0x81, 0x4d, 0xa8, 0xe0, 0x31, 0x4f, 0x19, 0xf0,
0x25, 0x49, 0x52, 0x0d, 0xba, 0x3b, 0xae, 0x50, 0xf2, 0x13, 0x25, 0x49, 0x24, 0x5c, 0x61, 0x88,
0x43, 0x49, 0x8d, 0xf6, 0x8f, 0x84, 0x84, 0x55, 0x16, 0x90, 0x50, 0x2b, 0x2a, 0xb4, 0xd0, 0xb4,
0x34, 0x04, 0xd9, 0x7d, 0x99, 0xca, 0x50, 0x9e, 0x2a, 0x73, 0xff, 0xb8, 0xc9, 0xa8, 0xdd, 0x3d,
0xfd, 0x93, 0xbf, 0xa8, 0x34, 0x8b, 0x41, 0x2a, 0x4e, 0x4d, 0xb8, 0xe2, 0x8a, 0xed, 0x72, 0x87,
0x6f, 0xc8, 0x3f, 0xb8, 0x66, 0x29, 0x48, 0xf6, 0x38, 0x0f, 0x1e, 0x78, 0x08, 0x57, 0x1c, 0xd8,
0x92, 0x01, 0xbb, 0x94, 0x06, 0xba, 0xb7, 0x7e, 0x5b, 0xd5, 0xb9, 0xf7, 0x6f, 0x88, 0x46, 0x9d,
0x29, 0x21, 0x4d, 0x1e, 0x4e, 0x1c, 0xed, 0x4c, 0xb3, 0xfd, 0x7c, 0x3b, 0xf0, 0xec, 0x76, 0xd0,
0xfe, 0x6a, 0x16, 0xdf, 0xc6, 0xee, 0x9d, 0xdf, 0x92, 0xc0, 0x95, 0xe9, 0xa1, 0xe1, 0xff, 0x51,
0x67, 0x7a, 0xde, 0x4c, 0xfd, 0xeb, 0xda, 0xd9, 0x5e, 0x7d, 0x4f, 0xeb, 0xc2, 0x19, 0x17, 0x95,
0x78, 0x36, 0xcf, 0x0b, 0xec, 0xad, 0x0b, 0xec, 0x6d, 0x0a, 0xec, 0xbd, 0x58, 0x8c, 0x72, 0x8b,
0xd1, 0xda, 0x62, 0xb4, 0xb1, 0x18, 0xbd, 0x5b, 0x8c, 0x5e, 0x3f, 0xb0, 0x77, 0x33, 0x6e, 0xfc,
0x0d, 0x3e, 0x03, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x0f, 0xd7, 0x36, 0x32, 0x02, 0x00, 0x00,
}
func (m *PartialObjectMetadataList) Marshal() (dAtA []byte, err error) {

View File

@ -33,9 +33,9 @@ message PartialObjectMetadataList {
// Standard list metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 2;
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 2;
// items contains each of the included items.
repeated k8s.io.apimachinery.pkg.apis.meta.v1.PartialObjectMetadata items = 1;
repeated .k8s.io.apimachinery.pkg.apis.meta.v1.PartialObjectMetadata items = 1;
}

View File

@ -19,4 +19,4 @@ limitations under the License.
//
// package testapigroup contains an testapigroup API used to demonstrate how to create api groups. Moreover, this is
// used within tests.
package testapigroup // import "k8s.io/apimachinery/pkg/apis/testapigroup"
package testapigroup

View File

@ -19,12 +19,12 @@ package fuzzer
import (
"fmt"
"github.com/google/gofuzz"
"sigs.k8s.io/randfill"
apitesting "k8s.io/apimachinery/pkg/api/apitesting"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
"k8s.io/apimachinery/pkg/apis/testapigroup"
"k8s.io/apimachinery/pkg/apis/testapigroup/v1"
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"k8s.io/apimachinery/pkg/runtime"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
)
@ -33,9 +33,9 @@ import (
// values in a Kubernetes context.
func overrideMetaFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(j *runtime.Object, c fuzz.Continue) {
func(j *runtime.Object, c randfill.Continue) {
// TODO: uncomment when round trip starts from a versioned object
if true { //c.RandBool() {
if true { // c.Bool() {
*j = &runtime.Unknown{
// We do not set TypeMeta here because it is not carried through a round trip
Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`),
@ -44,15 +44,15 @@ func overrideMetaFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
} else {
types := []runtime.Object{&testapigroup.Carp{}}
t := types[c.Rand.Intn(len(types))]
c.Fuzz(t)
c.Fill(t)
*j = t
}
},
func(r *runtime.RawExtension, c fuzz.Continue) {
func(r *runtime.RawExtension, c randfill.Continue) {
// Pick an arbitrary type and fuzz it
types := []runtime.Object{&testapigroup.Carp{}}
obj := types[c.Rand.Intn(len(types))]
c.Fuzz(obj)
c.Fill(obj)
// Convert the object to raw bytes
bytes, err := runtime.Encode(apitesting.TestCodec(codecs, v1.SchemeGroupVersion), obj)
@ -68,11 +68,11 @@ func overrideMetaFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
func testapigroupFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(s *testapigroup.CarpSpec, c fuzz.Continue) {
c.FuzzNoCustom(s)
func(s *testapigroup.CarpSpec, c randfill.Continue) {
c.FillNoCustom(s)
// has a default value
ttl := int64(30)
if c.RandBool() {
if c.Bool() {
ttl = int64(c.Uint32())
}
s.TerminationGracePeriodSeconds = &ttl
@ -81,11 +81,11 @@ func testapigroupFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
s.SchedulerName = "default-scheduler"
}
},
func(j *testapigroup.CarpPhase, c fuzz.Continue) {
func(j *testapigroup.CarpPhase, c randfill.Continue) {
statuses := []testapigroup.CarpPhase{"Pending", "Running", "Succeeded", "Failed", "Unknown"}
*j = statuses[c.Rand.Intn(len(statuses))]
},
func(rp *testapigroup.RestartPolicy, c fuzz.Continue) {
func(rp *testapigroup.RestartPolicy, c randfill.Continue) {
policies := []testapigroup.RestartPolicy{"Always", "Never", "OnFailure"}
*rp = policies[c.Rand.Intn(len(policies))]
},

View File

@ -46,6 +46,7 @@ func Resource(resource string) schema.GroupResource {
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Carp{},
&CarpList{},
)
return nil
}

View File

@ -68,6 +68,14 @@ type CarpStatus struct {
// This is before the Kubelet pulled the container image(s) for the carp.
// +optional
StartTime *metav1.Time
// Carp infos are provided by different clients, hence the map type.
//
// +listType=map
// +listKey=a
// +listKey=b
// +listKey=c
Infos []CarpInfo
}
type CarpCondition struct {
@ -83,6 +91,21 @@ type CarpCondition struct {
Message string
}
type CarpInfo struct {
// A is the first map key.
// +required
A int64
// B is the second map key.
// +required
B string
// C is the third, optional map key
// +optional
C *string
// Some data for each pair of A and B.
Data string
}
// CarpSpec is a description of a carp
type CarpSpec struct {
// +optional

View File

@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/testapigroup
// +k8s:openapi-gen=false
// +k8s:defaulter-gen=TypeMeta
// +k8s:prerelease-lifecycle-gen=true
// +groupName=testapigroup.apimachinery.k8s.io
package v1 // import "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
package v1

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto
// source: k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto
package v1
@ -48,7 +48,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
func (m *Carp) Reset() { *m = Carp{} }
func (*Carp) ProtoMessage() {}
func (*Carp) Descriptor() ([]byte, []int) {
return fileDescriptor_b7eb07c7d80facdf, []int{0}
return fileDescriptor_83e19b543dd132db, []int{0}
}
func (m *Carp) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -76,7 +76,7 @@ var xxx_messageInfo_Carp proto.InternalMessageInfo
func (m *CarpCondition) Reset() { *m = CarpCondition{} }
func (*CarpCondition) ProtoMessage() {}
func (*CarpCondition) Descriptor() ([]byte, []int) {
return fileDescriptor_b7eb07c7d80facdf, []int{1}
return fileDescriptor_83e19b543dd132db, []int{1}
}
func (m *CarpCondition) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -101,10 +101,38 @@ func (m *CarpCondition) XXX_DiscardUnknown() {
var xxx_messageInfo_CarpCondition proto.InternalMessageInfo
func (m *CarpInfo) Reset() { *m = CarpInfo{} }
func (*CarpInfo) ProtoMessage() {}
func (*CarpInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_83e19b543dd132db, []int{2}
}
func (m *CarpInfo) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *CarpInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (m *CarpInfo) XXX_Merge(src proto.Message) {
xxx_messageInfo_CarpInfo.Merge(m, src)
}
func (m *CarpInfo) XXX_Size() int {
return m.Size()
}
func (m *CarpInfo) XXX_DiscardUnknown() {
xxx_messageInfo_CarpInfo.DiscardUnknown(m)
}
var xxx_messageInfo_CarpInfo proto.InternalMessageInfo
func (m *CarpList) Reset() { *m = CarpList{} }
func (*CarpList) ProtoMessage() {}
func (*CarpList) Descriptor() ([]byte, []int) {
return fileDescriptor_b7eb07c7d80facdf, []int{2}
return fileDescriptor_83e19b543dd132db, []int{3}
}
func (m *CarpList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -132,7 +160,7 @@ var xxx_messageInfo_CarpList proto.InternalMessageInfo
func (m *CarpSpec) Reset() { *m = CarpSpec{} }
func (*CarpSpec) ProtoMessage() {}
func (*CarpSpec) Descriptor() ([]byte, []int) {
return fileDescriptor_b7eb07c7d80facdf, []int{3}
return fileDescriptor_83e19b543dd132db, []int{4}
}
func (m *CarpSpec) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -160,7 +188,7 @@ var xxx_messageInfo_CarpSpec proto.InternalMessageInfo
func (m *CarpStatus) Reset() { *m = CarpStatus{} }
func (*CarpStatus) ProtoMessage() {}
func (*CarpStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_b7eb07c7d80facdf, []int{4}
return fileDescriptor_83e19b543dd132db, []int{5}
}
func (m *CarpStatus) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -188,6 +216,7 @@ var xxx_messageInfo_CarpStatus proto.InternalMessageInfo
func init() {
proto.RegisterType((*Carp)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.Carp")
proto.RegisterType((*CarpCondition)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpCondition")
proto.RegisterType((*CarpInfo)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpInfo")
proto.RegisterType((*CarpList)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpList")
proto.RegisterType((*CarpSpec)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpSpec")
proto.RegisterMapType((map[string]string)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpSpec.NodeSelectorEntry")
@ -195,77 +224,81 @@ func init() {
}
func init() {
proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto", fileDescriptor_b7eb07c7d80facdf)
proto.RegisterFile("k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto", fileDescriptor_83e19b543dd132db)
}
var fileDescriptor_b7eb07c7d80facdf = []byte{
// 1051 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xc1, 0x6e, 0xdb, 0x46,
0x13, 0x36, 0x2d, 0xc9, 0x96, 0xd6, 0x56, 0x62, 0x6f, 0x62, 0x80, 0xbf, 0x81, 0x48, 0x8e, 0x0f,
0x86, 0xff, 0xc2, 0xa5, 0x62, 0xa3, 0x09, 0xdc, 0xe6, 0x50, 0x84, 0x76, 0x51, 0xbb, 0x70, 0x1c,
0x61, 0xe5, 0x22, 0x45, 0xd1, 0x43, 0x56, 0xd4, 0x54, 0x66, 0x25, 0x72, 0x89, 0xdd, 0x95, 0x0a,
0xdd, 0x8a, 0x3e, 0x41, 0x1f, 0xa2, 0xb7, 0x9e, 0xfb, 0x00, 0x3d, 0x14, 0xf0, 0x31, 0xc7, 0x9c,
0x84, 0x5a, 0x7d, 0x0b, 0x9f, 0x8a, 0x5d, 0x2e, 0x29, 0xca, 0x72, 0xd5, 0x28, 0x37, 0xee, 0xcc,
0xf7, 0x7d, 0x33, 0xbb, 0x33, 0x9a, 0x11, 0xfa, 0xba, 0x73, 0x28, 0x1c, 0x9f, 0xd5, 0x3a, 0xbd,
0x26, 0xf0, 0x10, 0x24, 0x88, 0x5a, 0x1f, 0xc2, 0x16, 0xe3, 0x35, 0xe3, 0xa0, 0x91, 0x1f, 0x50,
0xef, 0xd2, 0x0f, 0x81, 0x0f, 0x6a, 0x51, 0xa7, 0xad, 0x0c, 0xa2, 0x26, 0x41, 0x48, 0x1a, 0xf9,
0x6d, 0xce, 0x7a, 0x51, 0xad, 0xbf, 0x5f, 0x6b, 0x43, 0x08, 0x9c, 0x4a, 0x68, 0x39, 0x11, 0x67,
0x92, 0xe1, 0xbd, 0x98, 0xed, 0x64, 0xd9, 0x4e, 0xd4, 0x69, 0x2b, 0x83, 0x70, 0xb2, 0x6c, 0xa7,
0xbf, 0xbf, 0xf9, 0x71, 0xdb, 0x97, 0x97, 0xbd, 0xa6, 0xe3, 0xb1, 0xa0, 0xd6, 0x66, 0x6d, 0x56,
0xd3, 0x22, 0xcd, 0xde, 0xf7, 0xfa, 0xa4, 0x0f, 0xfa, 0x2b, 0x16, 0xdf, 0xfc, 0x64, 0x66, 0x6a,
0x01, 0x48, 0x7a, 0x47, 0x4a, 0x9b, 0xff, 0x7a, 0x21, 0xde, 0x0b, 0xa5, 0x1f, 0xc0, 0x14, 0xe1,
0xd9, 0x7f, 0x11, 0x84, 0x77, 0x09, 0x01, 0xbd, 0xcd, 0xdb, 0xfe, 0x75, 0x11, 0xe5, 0x8f, 0x28,
0x8f, 0xf0, 0x1b, 0x54, 0x54, 0xc9, 0xb4, 0xa8, 0xa4, 0xb6, 0xb5, 0x65, 0xed, 0xae, 0x1c, 0x3c,
0x71, 0x66, 0xbe, 0x8b, 0x42, 0x3b, 0xfd, 0x7d, 0xe7, 0x55, 0xf3, 0x07, 0xf0, 0xe4, 0x4b, 0x90,
0xd4, 0xc5, 0x57, 0xc3, 0xea, 0xc2, 0x68, 0x58, 0x45, 0x63, 0x1b, 0x49, 0x55, 0xf1, 0x37, 0x28,
0x2f, 0x22, 0xf0, 0xec, 0x45, 0xad, 0xfe, 0xcc, 0x99, 0xe7, 0xd5, 0x1d, 0x95, 0x63, 0x23, 0x02,
0xcf, 0x5d, 0x35, 0x31, 0xf2, 0xea, 0x44, 0xb4, 0x22, 0x7e, 0x83, 0x96, 0x84, 0xa4, 0xb2, 0x27,
0xec, 0x9c, 0xd6, 0x3e, 0xfc, 0x00, 0x6d, 0xcd, 0x77, 0xef, 0x19, 0xf5, 0xa5, 0xf8, 0x4c, 0x8c,
0xee, 0xf6, 0xef, 0x39, 0x54, 0x56, 0xb0, 0x23, 0x16, 0xb6, 0x7c, 0xe9, 0xb3, 0x10, 0x3f, 0x45,
0x79, 0x39, 0x88, 0x40, 0xbf, 0x55, 0xc9, 0x7d, 0x9c, 0x64, 0x75, 0x31, 0x88, 0xe0, 0x66, 0x58,
0x5d, 0x9f, 0x00, 0x2b, 0x23, 0xd1, 0x70, 0xfc, 0x69, 0x9a, 0xea, 0xe2, 0x04, 0xd1, 0x04, 0xbc,
0x19, 0x56, 0xef, 0xa7, 0xb4, 0xc9, 0x1c, 0x70, 0x1b, 0x95, 0xbb, 0x54, 0xc8, 0x3a, 0x67, 0x4d,
0xb8, 0xf0, 0x03, 0x30, 0x97, 0xfd, 0xe8, 0xfd, 0xca, 0xa4, 0x18, 0xee, 0x86, 0x89, 0x56, 0x3e,
0xcb, 0x0a, 0x91, 0x49, 0x5d, 0xdc, 0x47, 0x58, 0x19, 0x2e, 0x38, 0x0d, 0x45, 0x9c, 0xbf, 0x8a,
0x96, 0x9f, 0x3b, 0xda, 0xa6, 0x89, 0x86, 0xcf, 0xa6, 0xd4, 0xc8, 0x1d, 0x11, 0xf0, 0x0e, 0x5a,
0xe2, 0x40, 0x05, 0x0b, 0xed, 0x82, 0x7e, 0x9b, 0xb4, 0x18, 0x44, 0x5b, 0x89, 0xf1, 0xe2, 0xff,
0xa3, 0xe5, 0x00, 0x84, 0xa0, 0x6d, 0xb0, 0x97, 0x34, 0xf0, 0xbe, 0x01, 0x2e, 0xbf, 0x8c, 0xcd,
0x24, 0xf1, 0x6f, 0xff, 0x61, 0xa1, 0xa2, 0x2a, 0xc5, 0x99, 0x2f, 0x24, 0xfe, 0x6e, 0xaa, 0xc5,
0x9d, 0xf7, 0xbb, 0x8d, 0x62, 0xeb, 0x06, 0x5f, 0x33, 0x81, 0x8a, 0x89, 0x25, 0xd3, 0xde, 0xaf,
0x51, 0xc1, 0x97, 0x10, 0xa8, 0xc2, 0xe6, 0x76, 0x57, 0x0e, 0x0e, 0xe6, 0xef, 0x41, 0xb7, 0x6c,
0xe4, 0x0b, 0xa7, 0x4a, 0x88, 0xc4, 0x7a, 0xdb, 0x7f, 0x2e, 0xc7, 0x77, 0x50, 0x0d, 0x8f, 0xcf,
0x50, 0x99, 0x2b, 0x2a, 0x97, 0x75, 0xd6, 0xf5, 0xbd, 0x81, 0x6e, 0x82, 0x92, 0xbb, 0x93, 0x14,
0x96, 0x64, 0x9d, 0x37, 0xb7, 0x0d, 0x64, 0x92, 0x8c, 0xdb, 0xe8, 0x91, 0x04, 0x1e, 0xf8, 0x21,
0x55, 0x45, 0xf8, 0x92, 0x53, 0x0f, 0xea, 0xc0, 0x7d, 0xd6, 0x6a, 0x80, 0xc7, 0xc2, 0x96, 0xd0,
0x45, 0xcf, 0xb9, 0x8f, 0x47, 0xc3, 0xea, 0xa3, 0x8b, 0x59, 0x40, 0x32, 0x5b, 0x07, 0xbf, 0x42,
0x1b, 0xd4, 0x93, 0x7e, 0x1f, 0x8e, 0x81, 0xb6, 0xba, 0x7e, 0x08, 0x49, 0x80, 0x82, 0x0e, 0xf0,
0xbf, 0xd1, 0xb0, 0xba, 0xf1, 0xe2, 0x2e, 0x00, 0xb9, 0x9b, 0x87, 0x7f, 0xb6, 0xd0, 0x6a, 0xc8,
0x5a, 0xd0, 0x80, 0x2e, 0x78, 0x92, 0x71, 0x7b, 0x59, 0xbf, 0xfa, 0xc9, 0x87, 0x4d, 0x15, 0xe7,
0x3c, 0x23, 0xf5, 0x45, 0x28, 0xf9, 0xc0, 0x7d, 0x68, 0x5e, 0x74, 0x35, 0xeb, 0x22, 0x13, 0x31,
0xf1, 0x57, 0x08, 0x0b, 0xe0, 0x7d, 0xdf, 0x83, 0x17, 0x9e, 0xc7, 0x7a, 0xa1, 0x3c, 0xa7, 0x01,
0xd8, 0x45, 0x5d, 0x91, 0xb4, 0xf9, 0x1b, 0x53, 0x08, 0x72, 0x07, 0x0b, 0x9f, 0xa0, 0x7b, 0x93,
0x56, 0xbb, 0xa4, 0x75, 0xb6, 0x8c, 0x8e, 0x7d, 0x0c, 0x11, 0x07, 0x4f, 0x8d, 0xee, 0x49, 0x45,
0x72, 0x8b, 0x87, 0xf7, 0x50, 0x51, 0x65, 0xa9, 0x73, 0x41, 0x5a, 0x23, 0x6d, 0xdb, 0x73, 0x63,
0x27, 0x29, 0x02, 0x3f, 0x45, 0x2b, 0x97, 0x4c, 0xc8, 0x73, 0x90, 0x3f, 0x32, 0xde, 0xb1, 0x57,
0xb6, 0xac, 0xdd, 0xa2, 0xfb, 0xc0, 0x10, 0x56, 0x4e, 0xc6, 0x2e, 0x92, 0xc5, 0xa9, 0xdf, 0xa0,
0x3a, 0xd6, 0x4f, 0x8f, 0xed, 0x55, 0x4d, 0x49, 0x7f, 0x83, 0x27, 0xb1, 0x99, 0x24, 0xfe, 0x04,
0x7a, 0x5a, 0x3f, 0xb2, 0xcb, 0xd3, 0xd0, 0xd3, 0xfa, 0x11, 0x49, 0xfc, 0x2a, 0x75, 0xf5, 0x19,
0xaa, 0xd4, 0xd7, 0x26, 0x53, 0x3f, 0x31, 0x76, 0x92, 0x22, 0x70, 0x0d, 0x95, 0x44, 0xaf, 0xd9,
0x62, 0x01, 0xf5, 0x43, 0x7b, 0x5d, 0xc3, 0xd7, 0x0d, 0xbc, 0xd4, 0x48, 0x1c, 0x64, 0x8c, 0xc1,
0xcf, 0x51, 0x59, 0xad, 0xc1, 0x56, 0xaf, 0x0b, 0x5c, 0xc7, 0x78, 0xa0, 0x49, 0xe9, 0x54, 0x6c,
0x24, 0x4e, 0xfd, 0x46, 0x93, 0xd8, 0xcd, 0xcf, 0xd1, 0xfa, 0x54, 0x97, 0xe0, 0x35, 0x94, 0xeb,
0xc0, 0x20, 0x5e, 0x02, 0x44, 0x7d, 0xe2, 0x87, 0xa8, 0xd0, 0xa7, 0xdd, 0x1e, 0xc4, 0xf3, 0x9d,
0xc4, 0x87, 0xcf, 0x16, 0x0f, 0xad, 0xed, 0xdf, 0x72, 0x08, 0x8d, 0x57, 0x0d, 0x7e, 0x82, 0x0a,
0xd1, 0x25, 0x15, 0xc9, 0x06, 0x49, 0xfa, 0xa5, 0x50, 0x57, 0xc6, 0x9b, 0x61, 0xb5, 0xa4, 0xb0,
0xfa, 0x40, 0x62, 0x20, 0x66, 0x08, 0x79, 0xc9, 0x6e, 0x48, 0xc6, 0xcc, 0xf3, 0xf9, 0x1b, 0x3e,
0xdd, 0x2f, 0xe3, 0x7d, 0x9d, 0x9a, 0x04, 0xc9, 0x84, 0xc8, 0x0e, 0xda, 0xdc, 0xec, 0x41, 0x9b,
0x99, 0xdd, 0xf9, 0x99, 0xb3, 0x7b, 0x07, 0x2d, 0xc5, 0xc5, 0xbe, 0x3d, 0xe3, 0xe3, 0x5e, 0x20,
0xc6, 0xab, 0x70, 0x1e, 0xe5, 0xd1, 0x69, 0xdd, 0x8c, 0xf8, 0x14, 0x77, 0xa4, 0xad, 0xc4, 0x78,
0xf1, 0x6b, 0x54, 0xd2, 0x03, 0x4d, 0xaf, 0xa8, 0xe5, 0xb9, 0x57, 0x54, 0x59, 0xf7, 0x4a, 0x22,
0x40, 0xc6, 0x5a, 0x2e, 0xb9, 0xba, 0xae, 0x2c, 0xbc, 0xbd, 0xae, 0x2c, 0xbc, 0xbb, 0xae, 0x2c,
0xfc, 0x34, 0xaa, 0x58, 0x57, 0xa3, 0x8a, 0xf5, 0x76, 0x54, 0xb1, 0xde, 0x8d, 0x2a, 0xd6, 0x5f,
0xa3, 0x8a, 0xf5, 0xcb, 0xdf, 0x95, 0x85, 0x6f, 0xf7, 0xe6, 0xf9, 0xe3, 0xf9, 0x4f, 0x00, 0x00,
0x00, 0xff, 0xff, 0x1e, 0x86, 0x1f, 0x63, 0xc0, 0x0a, 0x00, 0x00,
var fileDescriptor_83e19b543dd132db = []byte{
// 1115 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0x41, 0x4f, 0x1b, 0x47,
0x14, 0x66, 0xb1, 0x0d, 0xf6, 0x80, 0x1b, 0x18, 0x82, 0xba, 0x45, 0x8a, 0x4d, 0x7c, 0x40, 0xb4,
0xa2, 0xeb, 0x80, 0x9a, 0x88, 0x36, 0x95, 0x2a, 0x16, 0xaa, 0x42, 0x45, 0x88, 0x35, 0x46, 0x4a,
0xd5, 0xe6, 0x90, 0xf1, 0xee, 0xb0, 0x6c, 0xf1, 0xee, 0xac, 0x66, 0xc6, 0xae, 0x7c, 0xab, 0x7a,
0xea, 0xb1, 0x3f, 0xa2, 0x7f, 0xa1, 0x3f, 0xa0, 0x37, 0x8e, 0x39, 0xa6, 0x17, 0xab, 0xb8, 0xff,
0x82, 0x53, 0x35, 0xb3, 0xb3, 0xeb, 0x35, 0x06, 0x27, 0xe6, 0xe6, 0x79, 0xef, 0xfb, 0xbe, 0xf7,
0xf6, 0xcd, 0x9b, 0xf7, 0x0c, 0xbe, 0xbe, 0xd8, 0xe5, 0x96, 0x4f, 0xeb, 0x38, 0xf2, 0x03, 0xec,
0x9c, 0xfb, 0x21, 0x61, 0xbd, 0x7a, 0x74, 0xe1, 0x49, 0x03, 0xaf, 0x0b, 0xc2, 0x05, 0x8e, 0x7c,
0x8f, 0xd1, 0x4e, 0x54, 0xef, 0x6e, 0xd7, 0x3d, 0x12, 0x12, 0x86, 0x05, 0x71, 0xad, 0x88, 0x51,
0x41, 0xe1, 0x56, 0xcc, 0xb6, 0xb2, 0x6c, 0x2b, 0xba, 0xf0, 0xa4, 0x81, 0x5b, 0x59, 0xb6, 0xd5,
0xdd, 0x5e, 0xfb, 0xdc, 0xf3, 0xc5, 0x79, 0xa7, 0x65, 0x39, 0x34, 0xa8, 0x7b, 0xd4, 0xa3, 0x75,
0x25, 0xd2, 0xea, 0x9c, 0xa9, 0x93, 0x3a, 0xa8, 0x5f, 0xb1, 0xf8, 0xda, 0x17, 0x13, 0x53, 0x0b,
0x88, 0xc0, 0xb7, 0xa4, 0xb4, 0x56, 0xbf, 0x8b, 0xc5, 0x3a, 0xa1, 0xf0, 0x03, 0x32, 0x46, 0x78,
0xf6, 0x3e, 0x02, 0x77, 0xce, 0x49, 0x80, 0x6f, 0xf2, 0x6a, 0x7f, 0xce, 0x82, 0xfc, 0x3e, 0x66,
0x11, 0x7c, 0x03, 0x8a, 0x32, 0x19, 0x17, 0x0b, 0x6c, 0x1a, 0xeb, 0xc6, 0xe6, 0xc2, 0xce, 0x13,
0x6b, 0x62, 0x5d, 0x24, 0xda, 0xea, 0x6e, 0x5b, 0x2f, 0x5b, 0x3f, 0x13, 0x47, 0xbc, 0x20, 0x02,
0xdb, 0xf0, 0xb2, 0x5f, 0x9d, 0x19, 0xf4, 0xab, 0x60, 0x68, 0x43, 0xa9, 0x2a, 0xfc, 0x01, 0xe4,
0x79, 0x44, 0x1c, 0x73, 0x56, 0xa9, 0x3f, 0xb3, 0xa6, 0xa9, 0xba, 0x25, 0x73, 0x6c, 0x46, 0xc4,
0xb1, 0x17, 0x75, 0x8c, 0xbc, 0x3c, 0x21, 0xa5, 0x08, 0xdf, 0x80, 0x39, 0x2e, 0xb0, 0xe8, 0x70,
0x33, 0xa7, 0xb4, 0x77, 0xef, 0xa1, 0xad, 0xf8, 0xf6, 0x47, 0x5a, 0x7d, 0x2e, 0x3e, 0x23, 0xad,
0x5b, 0xfb, 0x2b, 0x07, 0xca, 0x12, 0xb6, 0x4f, 0x43, 0xd7, 0x17, 0x3e, 0x0d, 0xe1, 0x53, 0x90,
0x17, 0xbd, 0x88, 0xa8, 0x5a, 0x95, 0xec, 0xc7, 0x49, 0x56, 0xa7, 0xbd, 0x88, 0x5c, 0xf7, 0xab,
0xcb, 0x23, 0x60, 0x69, 0x44, 0x0a, 0x0e, 0xbf, 0x4c, 0x53, 0x9d, 0x1d, 0x21, 0xea, 0x80, 0xd7,
0xfd, 0xea, 0x83, 0x94, 0x36, 0x9a, 0x03, 0xf4, 0x40, 0xb9, 0x8d, 0xb9, 0x68, 0x30, 0xda, 0x22,
0xa7, 0x7e, 0x40, 0xf4, 0xc7, 0x7e, 0xf6, 0x61, 0xd7, 0x24, 0x19, 0xf6, 0xaa, 0x8e, 0x56, 0x3e,
0xce, 0x0a, 0xa1, 0x51, 0x5d, 0xd8, 0x05, 0x50, 0x1a, 0x4e, 0x19, 0x0e, 0x79, 0x9c, 0xbf, 0x8c,
0x96, 0x9f, 0x3a, 0xda, 0x9a, 0x8e, 0x06, 0x8f, 0xc7, 0xd4, 0xd0, 0x2d, 0x11, 0xe0, 0x06, 0x98,
0x63, 0x04, 0x73, 0x1a, 0x9a, 0x05, 0x55, 0x9b, 0xf4, 0x32, 0x90, 0xb2, 0x22, 0xed, 0x85, 0x9f,
0x82, 0xf9, 0x80, 0x70, 0x8e, 0x3d, 0x62, 0xce, 0x29, 0xe0, 0x03, 0x0d, 0x9c, 0x7f, 0x11, 0x9b,
0x51, 0xe2, 0xaf, 0x71, 0x50, 0x94, 0x37, 0x71, 0x14, 0x9e, 0x51, 0xf8, 0x31, 0x30, 0xe2, 0xd6,
0xce, 0xd9, 0x25, 0x4d, 0x30, 0xf6, 0x90, 0x81, 0xa5, 0xa3, 0xa5, 0xaf, 0x23, 0x75, 0xd8, 0xc8,
0x68, 0xc1, 0x15, 0x60, 0x38, 0xea, 0xbb, 0x4b, 0x76, 0x41, 0x1a, 0xf7, 0x91, 0xe1, 0xc0, 0x75,
0x90, 0x57, 0x8f, 0x24, 0xa7, 0xec, 0x69, 0x3b, 0x1e, 0x60, 0x81, 0x91, 0xf2, 0xd4, 0xfe, 0x36,
0xe2, 0xa8, 0xc7, 0x3e, 0x17, 0xf0, 0xf5, 0xd8, 0xbb, 0xb2, 0x3e, 0xac, 0x84, 0x92, 0xad, 0x5e,
0xd5, 0x92, 0x0e, 0x51, 0x4c, 0x2c, 0x99, 0x37, 0xf5, 0x0a, 0x14, 0x7c, 0x41, 0x02, 0xd9, 0x4d,
0xb9, 0xcd, 0x85, 0x9d, 0x9d, 0xe9, 0x1b, 0xdf, 0x2e, 0x6b, 0xf9, 0xc2, 0x91, 0x14, 0x42, 0xb1,
0x5e, 0xed, 0x9f, 0xf9, 0xf8, 0x1b, 0xe4, 0x2b, 0x83, 0xc7, 0xa0, 0xcc, 0x24, 0x95, 0x89, 0x06,
0x6d, 0xfb, 0x4e, 0x4f, 0x7f, 0xfb, 0x46, 0xd2, 0x4d, 0x28, 0xeb, 0xbc, 0xbe, 0x69, 0x40, 0xa3,
0x64, 0xe8, 0x81, 0x47, 0x82, 0xb0, 0xc0, 0x0f, 0xb1, 0xbc, 0xf9, 0xef, 0x18, 0x76, 0x48, 0x83,
0x30, 0x9f, 0xba, 0x4d, 0xe2, 0xd0, 0xd0, 0xe5, 0xaa, 0xe2, 0x39, 0xfb, 0xf1, 0xa0, 0x5f, 0x7d,
0x74, 0x3a, 0x09, 0x88, 0x26, 0xeb, 0xc0, 0x97, 0x60, 0x15, 0x3b, 0xc2, 0xef, 0x92, 0x03, 0x82,
0xdd, 0xb6, 0x1f, 0x92, 0x24, 0x40, 0x41, 0x05, 0xf8, 0x64, 0xd0, 0xaf, 0xae, 0xee, 0xdd, 0x06,
0x40, 0xb7, 0xf3, 0xe0, 0x6f, 0x06, 0x58, 0x0c, 0xa9, 0x4b, 0x9a, 0xa4, 0x4d, 0x1c, 0x41, 0x99,
0x39, 0xaf, 0xaa, 0x7e, 0x78, 0xbf, 0x51, 0x66, 0x9d, 0x64, 0xa4, 0xbe, 0x0d, 0x05, 0xeb, 0xd9,
0x0f, 0x75, 0x45, 0x17, 0xb3, 0x2e, 0x34, 0x12, 0x13, 0x7e, 0x0f, 0x20, 0x27, 0xac, 0xeb, 0x3b,
0x64, 0xcf, 0x71, 0x68, 0x27, 0x14, 0x27, 0x38, 0x20, 0x66, 0x51, 0xdd, 0x48, 0xfa, 0xe2, 0x9a,
0x63, 0x08, 0x74, 0x0b, 0x0b, 0xbe, 0x06, 0xa6, 0x4b, 0x22, 0x46, 0x1c, 0xb9, 0x11, 0x46, 0x39,
0x66, 0x49, 0x29, 0xae, 0x6b, 0x45, 0xf3, 0xe0, 0x0e, 0x1c, 0xba, 0x53, 0x01, 0x6e, 0x81, 0xa2,
0xcc, 0x5c, 0xe5, 0x07, 0x94, 0x5a, 0xda, 0xca, 0x27, 0xda, 0x8e, 0x52, 0x04, 0x7c, 0x0a, 0x16,
0xce, 0x29, 0x17, 0x27, 0x44, 0xfc, 0x42, 0xd9, 0x85, 0xb9, 0xb0, 0x6e, 0x6c, 0x16, 0xed, 0x15,
0x4d, 0x58, 0x38, 0x1c, 0xba, 0x50, 0x16, 0x27, 0x87, 0x81, 0x3c, 0x36, 0x8e, 0x0e, 0xcc, 0x45,
0x45, 0x49, 0x87, 0xc1, 0x61, 0x6c, 0x46, 0x89, 0x3f, 0x81, 0x1e, 0x35, 0xf6, 0xcd, 0xf2, 0x38,
0xf4, 0xa8, 0xb1, 0x8f, 0x12, 0xbf, 0x4c, 0x5d, 0xfe, 0x0c, 0x65, 0xea, 0x4b, 0xa3, 0xa9, 0x1f,
0x6a, 0x3b, 0x4a, 0x11, 0xb0, 0x0e, 0x4a, 0xbc, 0xd3, 0x72, 0x69, 0x80, 0xfd, 0xd0, 0x5c, 0x56,
0xf0, 0x65, 0x0d, 0x2f, 0x35, 0x13, 0x07, 0x1a, 0x62, 0xe0, 0x73, 0x50, 0x96, 0xfb, 0xd8, 0xed,
0xb4, 0x09, 0x53, 0xe5, 0x59, 0x51, 0xa4, 0x74, 0x3c, 0x37, 0xb3, 0x4e, 0x34, 0x8a, 0x5d, 0xfb,
0x06, 0x2c, 0x8f, 0x75, 0x0e, 0x5c, 0x02, 0xb9, 0x0b, 0xd2, 0x8b, 0xb7, 0x11, 0x92, 0x3f, 0xe1,
0x43, 0x50, 0xe8, 0xe2, 0x76, 0x87, 0xc4, 0x93, 0x0d, 0xc5, 0x87, 0xaf, 0x66, 0x77, 0x8d, 0xda,
0xef, 0x79, 0x00, 0x86, 0x3b, 0x0f, 0x3e, 0x01, 0x85, 0xe8, 0x1c, 0xf3, 0x64, 0x95, 0x25, 0x3d,
0x54, 0x68, 0x48, 0xe3, 0x75, 0xbf, 0x5a, 0x92, 0x58, 0x75, 0x40, 0x31, 0x10, 0x52, 0x00, 0x9c,
0x64, 0x49, 0x25, 0xa3, 0xe7, 0xf9, 0xf4, 0x8f, 0x20, 0x5d, 0x74, 0xc3, 0x3f, 0x0e, 0xa9, 0x89,
0xa3, 0x4c, 0x88, 0xec, 0xc4, 0xcf, 0x4d, 0x9e, 0xf8, 0x99, 0x25, 0x92, 0x9f, 0xb8, 0x44, 0x36,
0xc0, 0x5c, 0x7c, 0xd9, 0x37, 0x97, 0x4d, 0xdc, 0x0b, 0x48, 0x7b, 0x25, 0xce, 0x91, 0x1b, 0xa4,
0xa1, 0x77, 0x4d, 0x8a, 0x53, 0x7b, 0xa5, 0x81, 0xb4, 0x17, 0xbe, 0x02, 0x25, 0x35, 0xe4, 0xd4,
0xae, 0x9c, 0x9f, 0x7a, 0x57, 0x96, 0x55, 0xaf, 0x24, 0x02, 0x68, 0xa8, 0x05, 0x7f, 0x02, 0x05,
0x3f, 0x3c, 0xa3, 0xdc, 0x2c, 0xaa, 0x3a, 0xdf, 0xe3, 0x7f, 0x93, 0xdc, 0x7e, 0x99, 0x31, 0x2f,
0xc5, 0x50, 0xac, 0x69, 0xa3, 0xcb, 0xab, 0xca, 0xcc, 0xdb, 0xab, 0xca, 0xcc, 0xbb, 0xab, 0xca,
0xcc, 0xaf, 0x83, 0x8a, 0x71, 0x39, 0xa8, 0x18, 0x6f, 0x07, 0x15, 0xe3, 0xdd, 0xa0, 0x62, 0xfc,
0x3b, 0xa8, 0x18, 0x7f, 0xfc, 0x57, 0x99, 0xf9, 0x71, 0x6b, 0x9a, 0xbf, 0xd7, 0xff, 0x07, 0x00,
0x00, 0xff, 0xff, 0xa8, 0x1e, 0x19, 0x20, 0x8d, 0x0b, 0x00, 0x00,
}
func (m *Carp) Marshal() (dAtA []byte, err error) {
@ -384,6 +417,49 @@ func (m *CarpCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *CarpInfo) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CarpInfo) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *CarpInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.C != nil {
i -= len(*m.C)
copy(dAtA[i:], *m.C)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.C)))
i--
dAtA[i] = 0x22
}
i -= len(m.Data)
copy(dAtA[i:], m.Data)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Data)))
i--
dAtA[i] = 0x1a
i -= len(m.B)
copy(dAtA[i:], m.B)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.B)))
i--
dAtA[i] = 0x12
i = encodeVarintGenerated(dAtA, i, uint64(m.A))
i--
dAtA[i] = 0x8
return len(dAtA) - i, nil
}
func (m *CarpList) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@ -573,6 +649,20 @@ func (m *CarpStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.Infos) > 0 {
for iNdEx := len(m.Infos) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Infos[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x42
}
}
if m.StartTime != nil {
{
size, err := m.StartTime.MarshalToSizedBuffer(dAtA[:i])
@ -674,6 +764,24 @@ func (m *CarpCondition) Size() (n int) {
return n
}
func (m *CarpInfo) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
n += 1 + sovGenerated(uint64(m.A))
l = len(m.B)
n += 1 + l + sovGenerated(uint64(l))
l = len(m.Data)
n += 1 + l + sovGenerated(uint64(l))
if m.C != nil {
l = len(*m.C)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
func (m *CarpList) Size() (n int) {
if m == nil {
return 0
@ -757,6 +865,12 @@ func (m *CarpStatus) Size() (n int) {
l = m.StartTime.Size()
n += 1 + l + sovGenerated(uint64(l))
}
if len(m.Infos) > 0 {
for _, e := range m.Infos {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
@ -793,6 +907,19 @@ func (this *CarpCondition) String() string {
}, "")
return s
}
func (this *CarpInfo) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&CarpInfo{`,
`A:` + fmt.Sprintf("%v", this.A) + `,`,
`B:` + fmt.Sprintf("%v", this.B) + `,`,
`Data:` + fmt.Sprintf("%v", this.Data) + `,`,
`C:` + valueToStringGenerated(this.C) + `,`,
`}`,
}, "")
return s
}
func (this *CarpList) String() string {
if this == nil {
return "nil"
@ -850,6 +977,11 @@ func (this *CarpStatus) String() string {
repeatedStringForConditions += strings.Replace(strings.Replace(f.String(), "CarpCondition", "CarpCondition", 1), `&`, ``, 1) + ","
}
repeatedStringForConditions += "}"
repeatedStringForInfos := "[]CarpInfo{"
for _, f := range this.Infos {
repeatedStringForInfos += strings.Replace(strings.Replace(f.String(), "CarpInfo", "CarpInfo", 1), `&`, ``, 1) + ","
}
repeatedStringForInfos += "}"
s := strings.Join([]string{`&CarpStatus{`,
`Phase:` + fmt.Sprintf("%v", this.Phase) + `,`,
`Conditions:` + repeatedStringForConditions + `,`,
@ -858,6 +990,7 @@ func (this *CarpStatus) String() string {
`HostIP:` + fmt.Sprintf("%v", this.HostIP) + `,`,
`CarpIP:` + fmt.Sprintf("%v", this.CarpIP) + `,`,
`StartTime:` + strings.Replace(fmt.Sprintf("%v", this.StartTime), "Time", "v1.Time", 1) + `,`,
`Infos:` + repeatedStringForInfos + `,`,
`}`,
}, "")
return s
@ -1263,6 +1396,172 @@ func (m *CarpCondition) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *CarpInfo) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CarpInfo: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CarpInfo: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field A", wireType)
}
m.A = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.A |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field B", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.B = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Data = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field C", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(dAtA[iNdEx:postIndex])
m.C = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *CarpList) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
@ -2140,6 +2439,40 @@ func (m *CarpStatus) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Infos", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Infos = append(m.Infos, CarpInfo{})
if err := m.Infos[len(m.Infos)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -33,7 +33,7 @@ message Carp {
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
// Specification of the desired behavior of the carp.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
@ -62,11 +62,11 @@ message CarpCondition {
// Last time we probed the condition.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastProbeTime = 3;
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastProbeTime = 3;
// Last time the condition transitioned from one status to another.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 4;
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 4;
// Unique, one-word, CamelCase reason for the condition's last transition.
// +optional
@ -77,12 +77,29 @@ message CarpCondition {
optional string message = 6;
}
message CarpInfo {
// A is the first map key.
// +required
optional int64 a = 1;
// B is the second map key.
// +required
optional string b = 2;
// C is the third, optional map key
// +optional
optional string c = 4;
// Some data for each pair of A and B.
optional string data = 3;
}
// CarpList is a list of Carps.
message CarpList {
// Standard list metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
// List of carps.
// More info: http://kubernetes.io/docs/user-guide/carps
@ -125,11 +142,11 @@ message CarpSpec {
// +optional
optional string serviceAccountName = 8;
// DeprecatedServiceAccount is a depreciated alias for ServiceAccountName.
// DeprecatedServiceAccount is a deprecated alias for ServiceAccountName.
// Deprecated: Use serviceAccountName instead.
// +k8s:conversion-gen=false
// +optional
optional string serviceAccount = 9;
optional string deprecatedServiceAccount = 9;
// NodeName is a request to schedule this carp onto a specific node. If it is non-empty,
// the scheduler simply schedules this carp onto that node, assuming that it fits resource
@ -138,7 +155,6 @@ message CarpSpec {
optional string nodeName = 10;
// Host networking requested for this carp. Use the host's network namespace.
// If this option is set, the ports that will be used must be specified.
// Default to false.
// +k8s:conversion-gen=false
// +optional
@ -169,7 +185,7 @@ message CarpSpec {
// If specified, the carp will be dispatched by specified scheduler.
// If not specified, the carp will be dispatched by default scheduler.
// +optional
optional string schedulername = 19;
optional string schedulerName = 19;
}
// CarpStatus represents information about the status of a carp. Status may trail the actual
@ -182,6 +198,10 @@ message CarpStatus {
// Current service state of carp.
// More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions
// +patchStrategy=merge
// +patchMergeKey=type
// +listType=map
// +listMapKey=type
// +optional
repeated CarpCondition conditions = 2;
@ -206,6 +226,14 @@ message CarpStatus {
// RFC 3339 date and time at which the object was acknowledged by the Kubelet.
// This is before the Kubelet pulled the container image(s) for the carp.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time startTime = 7;
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time startTime = 7;
// Carp infos are provided by different clients, hence the map type.
//
// +listType=map
// +listMapKey=a
// +listMapKey=b
// +listMapKey=c
repeated CarpInfo infos = 8;
}

View File

@ -57,6 +57,7 @@ func init() {
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Carp{},
&CarpList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

Some files were not shown because too many files have changed in this diff Show More